理系学生のちらしの裏

日記だったり雑記だったり

【競プロ】どのディレクトリからでもすぐに競プロできるようなシェルスクリプトを書いた

いつでもどこでもすぐに競プロの問題解き始められるようなシェルスクリプトを書きました。イメージとしては、サイト名、コンテスト名、問題名を入力すると対応するディレクトリに移動して、テンプレファイルからC++ファイルを作成、問題のサンプルテストケースを取得してinファイルに書き込む、といった感じです。

コードはこちら

https://github.com/hpano/CompetitiveProgramming/blob/master/compp.sh

実際の動作はこんな感じ。全然関係ないディレクトリから、AtcoderのABC173のディレクトリに移り、A問題とB問題のサンプルテストケースを取得しています。

f:id:Pano:20200906160518g:plain

この問題のコンテストページはこちら

atcoder.jp


さてコードを見ていきましょう。まずは入力部分です。

ここでは、-dオプションの後に続けられたものをディレクトリ名として配列directorys、-fオプションの後に続けられたものを作成したいファイル名として配列filesに突っ込んでいます。

while test "$1" != ""; do
  case $1 in
    -*)
      if [[ "$1" =~ 'h' ]] || [[ "$1" =~ '-help' ]]; then
        hflag=true
      elif [[ "$1" =~ 'd' ]] || [[ "$1" =~ '-directory' ]]; then
        while [[ $2 != -* ]] && [[ $2 != "" ]]; do
          directorys+=($2)
          shift
        done
      elif [[ "$1" =~ 'f' ]] || [[ "$1" =~ '-file' ]]; then
        while [[ $2 != -* ]] && [[ $2 != "" ]]; do
          if [[ $2 =~ "." ]] && [[ $2 != *.cpp ]]; then
            echo "[compp] Invalid file $2 !"
            echo "        You can input *.cpp or *"
            exit 1
          fi
          files+=(${2%.cpp})
          shift
        done
      else
        echo "[compp] Invalid option $1 !"
        usage
        exit 1
      fi
      shift
      ;;
    *)
      echo "[compp] Invalid input $1 !"
      usage
      exit 1
      ;;
  esac
done

directorys=(${directorys[@]//// })


続いて競プロ用のルートディレクトリのパスを調べます。どこからでもbashファイル実行できるようにこのディレクトリのパスを通してあるので、whichコマンドでパスを取得します。一応パスを通していなくても使えるよう、その場合はパスの入力を求めます。ここで"n"を入力すると、今いるディレクトリを競プロ用のルートディレクトリとみなします。

declare rootpath=$(which compp.sh)
if [[ ${rootpath} == "" ]]; then
  read -p "[compp] Input competitive programing directory's root path or 'n' to give up cd: " rootpath
fi
if [[ ${rootpath} == "n" ]]; then
  rootpath=$(pwd)
elif [[ ${rootpath: -3} == ".sh" ]]; then
  rootpath=${rootpath%\/*}
fi


そしてそのディレクトリに移動し、目的のディレクトリまでさらに移動します。競プロ用のルートディレクトリから順に、directorysに入っているディレクトリを辿っていき(なければ作成するか選択でき)、たどり着いた場所にファイルを作成するといった感じです。

declare yn
cd ${rootpath}
for dir in ${directorys[@]}; do
  if [[ -d ${dir} ]]; then
    cd ${dir}
  else
    read -p "[compp] Directory '${dir}' not exists. make? [y/n]: " yn
    if [[ ${yn} == "y" ]]; then
      mkdir ${dir} && cd ${dir}
    fi
  fi
done
echo "[compp] Now at $(pwd)"


最後に前回の記事で作成したadd.shを使い、配列filesに入っているファイルを作成します。こちらもパスが通っていなければ、add.shのパスの入力を求めます。

declare addpath=$(which add.sh)
if [[ ${addpath} == "" ]]; then
  read -p "[compp] Input add.sh path or 'n' to give up add file: " addpath
fi
if [[ ${addpath} == "n" ]]; then
  exit 1
elif [[ ${addpath: -3} != ".sh" ]]; then
  if [[ ${addpath: -1} != "/" ]]; then
    addpath+="/"
  fi
  addpath+="add.sh"
fi

${addpath} ${files[@]}


このシェルスクリプトの中でcdをしてディレクトリの移動をしているのですが、シェルスクリプトは基本的には現在のシェルとは別のシェルで実行されるので、普通に実行しただけだと現在のシェルを移動させることはできません。実行する際に.コマンドかsourceコマンドを使用する必要があります。

こちらのサイトが参考になりました。

www.pc-gear.com

毎回.を打つのも忘れそうだしめんどくさいので、~/.bashrcに.コマンドありでこのシェルスクリプトを実行する関数を用意しました。

# competitive programing by hpano
compp(){
    . compp.sh $@
}

これで実行する際にはcompp -d [ディレクトリ] -f [ファイル]と入力することで、どこからでも実行できるようになりました。


また、ここまでで色々パスを通したり、.bashrcに書き込んだり、シェルスクリプトパーミッションを変更したりと色々設定があって忘れそうなので、それら設定を一括で行えるsetting.shを作成しました。

https://github.com/hpano/CompetitiveProgramming/blob/master/setting.sh

#/bin/bash

function write2bash_profile() {
  local bashpro=$(cat ~/.bash_profile)
  if [[ !(${bashpro} =~ "# competitive programing by hpano") ]]; then
    local yn
    read -p "[setting] Write PATHs to ~/.bash_profile now? [y/n]: " yn
    while [[ ${yn} != "y" ]] && [[ ${yn} != "n" ]]; do
      read -p "Answer only y or n [y/n]: " yn
    done
    if [[ ${yn} == "y" ]]; then
      echo "# competitive programing by hpano" >> ~/.bash_profile
      echo "export PATH=\$PATH:${dirpath}" >> ~/.bash_profile
      source ~/.bash_profile
      echo "[setting] Wrote PATHs to ~/.bash_profile"
    fi
  fi
}

function write2bashrc() {
  local bashrc=$(cat ~/.bashrc)
  if [[ !(${bashrc} =~ "# competitive programing by hpano") ]]; then
    local yn
    read -p "[setting] Write functions to ~/.bashrc now? [y/n]: " yn
    while [[ ${yn} != "y" ]] && [[ ${yn} != "n" ]]; do
      read -p "Answer only y or n [y/n]: " yn
    done
    if [[ ${yn} == "y" ]]; then
      echo "# competitive programing by hpano" >> ~/.bashrc
      echo "compp(){" >> ~/.bashrc
      echo "    . compp.sh $@" >> ~/.bashrc
      echo "}" >> ~/.bashrc
      source ~/.bashrc
      echo "[setting] Wrote functions to ~/.bashrc"
    fi
  fi
}

declare dirpath=$(dirname $0)

write2bash_profile
write2bashrc
chmod +x ${dirpath}/setting.sh
chmod +x ${dirpath}/add.sh
chmod +x ${dirpath}/compile.sh
chmod +x ${dirpath}/compp.sh
chmod +x ${dirpath}/remove.sh
chmod +x ${dirpath}/run.sh

echo "[setting] Finish setting"


これでますます競プロが捗るはずです。頑張って精進します。

【競プロ】サンプルテストケースを取得してくるシェルスクリプトを書いた

前の記事でinファイルにサンプルテストケースの入力例と出力例を自動で書き込めるようにしたいと書きましたが、できるようになりました。 現時点ではAtcoder、AOJ(Arenaを除く)、yukicoderに対応しています。今回のも前回と同様githubに上げてあります。

https://github.com/hpano/CompetitiveProgramming/blob/master/add.sh

動作はこんな感じ。例としてAtcoderのABC173のA問題とB問題のサンプルテストケースを取得してきています。

f:id:Pano:20200906000527g:plain

コンテストページはこちら

atcoder.jp


さて順番にコードを見ていきましょう。

まず、-rオプションがある場合はすでにあるinファイルを削除するので、フラグを立てます。そして作りたいファイル名を順に配列に突っ込んでいきます。

declare -a argv=()
declare rflag=false
while test "$1" != ""; do
  case $1 in
    -*)
      if [[ "$1" =~ 'r' ]] || [[ "$1" =~ '-remove' ]]; then
        rflag=true
      fi
      shift
      ;;
    *)
      argv=("${argv[@]}" "${1%.cpp}")
      shift
      ;;
  esac
done


そして各ファイル名のC++ファイルを、テンプレートC++ファイルをコピーして作成します。またinファイル用のデータを取得するために、パスのディレクトリ名から競プロサイトを判断して後の処理につなげます。

この先はどのサイトでも大筋は同じなので、例としてAtcoderの場合を見ていきましょう。

for name in ${argv[@]}; do
  if [ -f ${name}.cpp ]; then
    echo "[add] ${name}.cpp already exists"
    continue
  fi

  cp $(dirname $0)/template.cpp ${name}.cpp && \
  echo "[add] Added ${name}.cpp"
done

declare path=( $(pwd | sed -e "s/.*competitive_programming\///g" | tr "/" " ") )
case ${path[0]} in
  "atcoder")
    get_atcoder
    ;;
  "AOJ")
    get_aoj
    ;;
  "yukicoder")
    get_yukicoder
    ;;
  *)
    echo "[add] ${path[0]} is unsupported"
    exit 1
    ;;
esac


まず実行ディレクトリまでのパスからコンテスト名を判断していき、URLに足していきます。例えばABC173の場合は次のような感じです。

"https://atcoder.jp/contests/" + "abc" + "173" + "/tasks/" + "abc" + "173_" + "問題ID[a-f]"

Atcoderの場合はABC、ARC、AGCは概ねこの規則通りなのでいいのですが、企業コンなどそれ以外の場合はちょっと異なるので、上記と異なる処理が必要です。

まずは上記と同じように処理していき、curlを使って一度HTTPリクエストを投げます。これでデータが取得できればそれでOKなのですが、できなかった場合、URLの最後に付いてるbeginnerを除去してもう一度curlします。URL構築の方法上、コンテスト名と同じものが"/tasks/"の後に追加されるのですが、コンテスト名にbeginnerが付いている場合でもURLの"/tasks/"の後には付いていないので(例えば下のコンテストなど)。

atcoder.jp

それでも404が返ってくる場合、readコマンドでURLの最後の部分を直接入力させます。

function get_atcoder() {
  local url_pre="https://atcoder.jp/contests/"
  # URLの前処理 企業コンなどotherの場合は特別
  if [ ${path[1]} = "other" ]; then
    url_pre+=$(echo ${path[2]} | tr "[A-Z]" "[a-z]")
    url_pre+="/tasks/"
    url_pre+=$(echo ${path[2]}_ | tr "[A-Z-]" "[a-z_]")
    local tmp_status=$(curl -Ls ${url_pre}a -o /dev/null -w '%{http_code}\n')
    if [ ${tmp_status} -eq 404 ]; then
      if [[ ${url_pre} =~ "beginner" ]]; then
        url_pre=${url_pre%%_beginner*}
        url_pre+="_"
      fi
      tmp_status=$(curl -Ls ${url_pre}a -o /dev/null -w '%{http_code}\n')
    fi
    if [ ${tmp_status} -eq 404 ]; then
      local url_in
      url_pre="${url_pre%\/*}/"
      read -p "URL? > ${url_pre}" url_in
      if [ "${url_in: -1}" != "_" ]; then
        if [[ "${url_in: -2}" =~ _[a-z] ]]; then
          url_in=${url_in%_*}
        fi
        url_in+="_"
      fi
      url_pre+=${url_in}
    fi
  else
    url_pre+=$(echo ${path[1]} | tr "[A-Z]" "[a-z]")
    url_pre+=$(echo "${path[2]}/tasks/")
    url_pre+=$(echo ${path[1]} | tr "[A-Z]" "[a-z]")
    url_pre+=$(echo "${path[2]}_")
  fi


ここから入力された各ファイル名について処理していきます。

まず、すでにinファイルが存在する場合、-rオプションが入力されていればそのinファイルを削除し、入力されていなければcontinueして次のファイルに移ります。

  # 各問題に対してデータ取得とinファイル作成
  for name in ${argv[@]}; do
    if [ -f ${name}.in ]; then
      if "${rflag}"; then
        rm ${name}.in
      else
        echo "[add] ${name}.in already exists"
        continue
      fi
    fi


続いてURLの最終決定です。昔のABCとかだと問題IDが[a-f]ではなく[1-6]になっていたりするので、それに対応するためにurl2として用意します。urlを試して404の場合url2を試すといった感じですね。それでもステータスが200でなかった場合、最終的にここでURLを全て入力してもらいます。もしくは、"n"を入力するとその問題を飛ばすことができます。

    local url=${url_pre}
    url+=${name}
    local url2=${url_pre}
    url2+=$(echo ${name} | tr "[a-f]" "[1-6]")
    local url2flag=false
    local continue_flag=false
    local status=$(curl -Ls ${url} -o /dev/null -w '%{http_code}\n')
    if [ ${status} -eq 404 ]; then
      url2flag=true
      sleep 0.5
      status=$(curl -Ls ${url2} -o /dev/null -w '%{http_code}\n')
    fi
    while [ ${status} -ne 200 ]; do
      echo "[add] Cannot recieve contents from ${url} (code: ${status})"
      read -p "Input correct URL or 'n' to give up getting: " url
      status=$(curl -Ls ${url} -o /dev/null -w '%{http_code}\n')
      url2flag=false
      if [[ ${url} == "n" ]]; then
        continue_flag=true
        break
      fi
    done
    if "${continue_flag}"; then
      continue
    fi
    if "${url2flag}"; then
      url=${url2}
    fi


そして得られたHTMLから必要な範囲だけを抽出します。どの部分が必要かは各競プロサイトのソースを見てみましょう。

    local data=$(curl -Lso- ${url})
    data=${data#*<span class=\"lang-ja\">}
    data=${data%%</span>*}
    local inflag=false
    local outflag=false
    touch ${name}.in

    echo "[add] Get from ${url}"


最後に残ったHTMLを1行ずつ見ていき、入力例or出力例の部分かを判断して、各テストケース毎に次のようにinファイルに書き込んで終わりです。

<in>
入力例1
</in>
<out>
出力例1
</out>

    while read line; do
      line=`echo ${line} | tr -d "\r"`
      if "${inflag}"; then
        if [[ ${line} =~ "</pre>" ]]; then
          inflag=false
          echo "</in>" >> ${name}.in
        else
          echo ${line} >> ${name}.in
        fi
      elif "${outflag}"; then
        if [[ ${line} =~ "</pre>" ]]; then
          outflag=false
          echo "</out>" >> ${name}.in
        else
          echo ${line} >> ${name}.in
        fi
      elif [[ ${line} =~ "<h3>入力例" ]]; then
        echo "<in>" >> ${name}.in
        inflag=true
        echo ${line#*<pre>} >> ${name}.in
      elif [[ ${line} =~ "<h3>出力例" ]]; then
        echo "<out>" >> ${name}.in
        outflag=true
        echo ${line#*<pre>} >> ${name}.in
      fi
    done << END
    ${data}
END

    echo "[add] Added ${name}.in"
  done
}

サンプルテストケースをコピペする手間が省けてかなり便利になりました。

【競プロ】コンパイルとテストケースの確認できるシェルスクリプトを書いた

タイトルの通りです。競プロでコード書いていていちいちコンパイルしたりテストケース試したりってのがめんどくさいなと思ったので、書いてみました。

以下全文です。githubにも上げてあります。(途中からシンタックスハイライトがおかしくなってるのは気にしないでください)

https://github.com/hpano/CompetitiveProgramming/blob/master/run.sh

まあこんなことをしなくてもonline-judge-toolsという便利なツールがあるので、特にこだわりがなければこれを使うのがいいでしょう。

online-judge-tools.readthedocs.io

#!/bin/bash

function usage() {
  echo "Usage: $0 [OPTIONS] FILE"
  echo
  echo "Options:"
  echo "  -h, --help           Display available options."
  echo "  -c, --compile        Compile the FILE."
  echo "  -i, --input INPUT    Specify input file as INPUT."
  echo "  -n, --norun          Not run code test."
  echo "  -r, --remove         Remove the existing executable file."
  echo
  exit 1
}

declare -i argc=0
declare -a argv=()
declare hflag=false
declare cflag=false
declare iflag=false
declare nflag=false
declare rflag=false
declare in_file

while test "$1" != ""; do
  case $1 in
    -*)
      if [[ "$1" =~ 'h' ]] || [[ "$1" =~ '-help' ]]; then
        hflag=true
      fi
      if [[ "$1" =~ 'c' ]] || [[ "$1" =~ '-compile' ]]; then
        cflag=true
      fi
      if [[ "$1" =~ 'n' ]] || [[ "$1" =~ '-norun' ]]; then
        nflag=true
      fi
      if [[ "$1" =~ 'r' ]] || [[ "$1" =~ '-remove' ]]; then
        rflag=true
      fi
      if [[ "$1" =~ 'i' ]] || [[ "$1" =~ '-input' ]]; then
        iflag=true
        in_file=$2
        shift
      fi
      shift
      ;;
    *)
      ((++argc))
      argv=("${argv[@]}" "${1%.cpp}")
      shift
      ;;
  esac
done

for i in ${argv[@]}; do
  declare eval cmissflag_${i}=false
done

if "${hflag}"; then
  usage
  exit
fi
if "${rflag}"; then
  $(dirname $0)/remove.sh ${argv[@]}
fi
if "${cflag}"; then
  for i in ${argv[@]}; do
    $(dirname $0)/compile.sh ${i} || eval cmissflag_${i}=true
  done
fi
if "${nflag}"; then
  exit
fi

for i in ${argv[@]}; do
  if eval '$cmissflag_'${i}; then
    continue
  fi
  if ! "${iflag}"; then
    in_file=${i}.in
  fi

  declare -a input=()
  declare -a output=()
  declare inflag=false
  declare outflag=false
  declare case_count=0
  declare ac_count=0
  declare wa_count=0
  declare slowest_time=0
  declare slowest_case

  echo "[run] Run ${i}.out < ${in_file}"
  # echo

  while read line; do
    if [ "${line}" == "<in>" ]; then
      inflag=true
      continue
    elif [ "${line}" == "</in>" ]; then
      inflag=false
      continue
    elif [ "${line}" == "<out>" ]; then
      outflag=true
      continue
    elif [ "${line}" == "</out>" ]; then
      outflag=false
      output[${case_count}]=`echo -e "${output[${case_count}]}"`
      case_count=$(( case_count + 1 ))
      continue
    fi

    if "${inflag}"; then
      input[${case_count}]+="${line}"" "
    elif "${outflag}"; then
      output[${case_count}]+="${line}""\n"
    fi
  done < ./${in_file}

  for j in `seq 0 $(( case_count - 1))`; do
    echo "[run] *** case `expr ${j} + 1` ***"
    # echo "[run] input:"
    # echo -e "${input[${j}]}"
    # echo "[run] output:"
    # echo -e "${output[${j}]}"

    declare start_time=`gdate +%s.%6N`
    declare result=$(./${i}.out << EOT
    ${input[${j}]}
    EOT)
    declare end_time=`gdate +%s.%6N`
    declare process_time=`echo "scale=6; (${end_time} - ${start_time})" | bc`
    if [ `echo "${process_time} > ${slowest_time}" | bc` == 1 ]; then
      slowest_time=${process_time}
      slowest_case=${j}
    fi

    echo "[run] time: ${process_time} sec"
    if [ "${output[${j}]}" = "${result}" ]; then
      ac_count=$(( ac_count + 1 ))
      echo -e "[run] \033[0;37;42m AC \033[0;39m"
    else
      wa_count=$(( wa_count + 1 ))
      echo -e "[run] \033[0;37;43m WA \033[0;39m"
      echo "output:"
      echo -e "${result}"
      echo "expected:"
      echo -e "${output[${j}]}"
    fi
  done

  echo "[run] slowest: ${slowest_time} sec  (case ${slowest_case})"
  echo "[run] AC rate: ${ac_count} / $(( ac_count + wa_count )) (AC / cases)"
done

コンパイルして欲しいことがあったりなかったり、あらかじめ用意したテストケースを試して欲しかったり欲しくなかったりすることがあると思うので、オプションで指定できるようにしました。まずこの部分でどのオプションが指定されているかフラグを立てています。-iオプションの場合はファイル指定があるので後に続くファイル名を読み取っています。オプション文字列にそのオプションに対応する文字が含まれているのかどうかで判定しているので、複数オプションをつなげて書いても認識できます(-cn など)。

また、オプション以外は全てテストしたいプログラムのファイル名として受け取り、一応複数ファイル同時にテストできるようにしてあります(実際にすることはなさそう)。

while test "$1" != ""; do
  case $1 in
    -*)
      if [[ "$1" =~ 'h' ]] || [[ "$1" =~ '-help' ]]; then
        hflag=true
      fi
      if [[ "$1" =~ 'c' ]] || [[ "$1" =~ '-compile' ]]; then
        cflag=true
      fi
      if [[ "$1" =~ 'n' ]] || [[ "$1" =~ '-norun' ]]; then
        nflag=true
      fi
      if [[ "$1" =~ 'r' ]] || [[ "$1" =~ '-remove' ]]; then
        rflag=true
      fi
      if [[ "$1" =~ 'i' ]] || [[ "$1" =~ '-input' ]]; then
        iflag=true
        in_file=$2
        shift
      fi
      shift
      ;;
    *)
      ((++argc))
      argv=("${argv[@]}" "${1%.cpp}")
      shift
      ;;
  esac
done

続いてこの部分で各フラグに対応する処理をしています。-rオプションの場合は実行ファイルを削除するシェルスクリプト remove.shを実行、-cオプションの場合はコンパイルを行うシェルスクリプト compile.shを全ての入力プログラムに対して実行します。ここでコンパイルに失敗した場合は、それぞれのプログラムに対応したコンパイル失敗フラグを立てておきます。

for i in ${argv[@]}; do
  declare eval cmissflag_${i}=false
done

if "${hflag}"; then
  usage
  exit
fi
if "${rflag}"; then
  $(dirname $0)/remove.sh ${argv[@]}
fi
if "${cflag}"; then
  for i in ${argv[@]}; do
    $(dirname $0)/compile.sh ${i} || eval cmissflag_${i}=true
  done
fi
if "${nflag}"; then
  exit
fi

その後の部分でコンパイルに成功した(orコンパイル済みの)各プログラムを実行していきます。
まずはこの部分でテストケースの入力ファイルを解析。で囲われた部分を入力、で囲われた部分を期待する出力として、それぞれ配列につっこんでいきます。

  while read line; do
    if [ "${line}" == "<in>" ]; then
      inflag=true
      continue
    elif [ "${line}" == "</in>" ]; then
      inflag=false
      continue
    elif [ "${line}" == "<out>" ]; then
      outflag=true
      continue
    elif [ "${line}" == "</out>" ]; then
      outflag=false
      output[${case_count}]=`echo -e "${output[${case_count}]}"`
      case_count=$(( case_count + 1 ))
      continue
    fi

    if "${inflag}"; then
      input[${case_count}]+="${line}"" "
    elif "${outflag}"; then
      output[${case_count}]+="${line}""\n"
    fi
  done < ./${in_file}

続く部分でプログラムを実行。配列につっこんだ各テストケースの入力を取り出してプログラムに渡し、出力結果を受け取ります。結果が期待する出力と等しければAC、違っていればWAとして、結果と期待する出力を表示します。また、計測した実行時間も表示します。

  for j in `seq 0 $(( case_count - 1))`; do
    echo "[run] *** case `expr ${j} + 1` ***"
    # echo "[run] input:"
    # echo -e "${input[${j}]}"
    # echo "[run] output:"
    # echo -e "${output[${j}]}"

    declare start_time=`gdate +%s.%6N`
    declare result=$(./${i}.out << EOT
    ${input[${j}]}
    EOT)
    declare end_time=`gdate +%s.%6N`
    declare process_time=`echo "scale=6; (${end_time} - ${start_time})" | bc`
    if [ `echo "${process_time} > ${slowest_time}" | bc` == 1 ]; then
      slowest_time=${process_time}
      slowest_case=${j}
    fi

    echo "[run] time: ${process_time} sec"
    if [ "${output[${j}]}" = "${result}" ]; then
      ac_count=$(( ac_count + 1 ))
      echo -e "[run] \033[0;37;42m AC \033[0;39m"
    else
      wa_count=$(( wa_count + 1 ))
      echo -e "[run] \033[0;37;43m WA \033[0;39m"
      echo "output:"
      echo -e "${result}"
      echo "expected:"
      echo -e "${output[${j}]}"
    fi
  done

最後に最遅だったテストケースとその実行時間、AC数とWA数を表示して終了です。

  echo "[run] slowest: ${slowest_time} sec  (case ${slowest_case})"
  echo "[run] AC rate: ${ac_count} / $(( ac_count + wa_count )) (AC / cases)"

実行するとこんな感じになります。 f:id:Pano:20200903001113p:plain

これでまあまあ便利に競プロやれるようになりました。あとは、inファイルのテストケースの入力と出力を手でコピペしているので、そこを自動でやってくれるようにしたいですね。

時が経つのは早いもので

久しぶりの更新です。得意の三日坊主が発動して、しばらく更新していませんでした。

 

さて、時が経つのは早いもので、気付いたら大学院1年になってしまいました。あっという間ですね。この春学期も、研究したり、授業受けたり、課題をやったりしていたら終わってしまいました。普通にいけばあと1年半で社会人になっているはずですが、まずいですね。学生生活が終わってしまいます。幸いにもあと取らないといけない授業は最低限1つなので、今後はもうちょっと他のことにも取り組めるでしょう、きっと。というか取り組みたい。サークルを去年で引退して今までよりは時間に余裕ができたこともあり、7月頃から今までやろうと思っていたけどできなかったことに手を出し始めました。ということで、今回はそれらについて軽く書こうかな。(そのうち研究についても書こうと思ってます。)

 

競プロ

去年まではサークルの都合で土日がほとんど埋まっていたのですが、今年はコンテストに参加できるということで始めました。あと研究室の先輩の影響もあります。とりあえずAtCoderの問題を解いててABCレベルのコンテストにも2回参加したのですが、まだまだですね。D問題までなら解けますが、解く時間が遅いし、EF問題には全然手が出ません。AtCoder Problemsなどを活用して精進します。

atcoder.jp

 

競プロの入門については、これを読むのがいいと思います。

qiita.com

 

ゲーム開発

前からやりたかったゲーム開発をちょこちょこやっています。といってもまあUnityの勉強が大半を占めていますが。色々と作りたいゲームは頭の中にあるので、完成までもっていきたいです。(ゲーム開発は完成しないことがほとんどなので。。。)そうそう、ついでにだいぶ前に買ったこの本を一通り読みました。元々5年前くらいにMinecraft PEのMod開発をしていた頃に買ったやつだったのですが、ちゃんと読んでいなかったので。Minecraft PEのMod開発はプログラミングを始めたきっかけなので、ちゃんと読もうと思って5年の時を経て読みました。

 

では今回はこの辺で。

Windows10でプログラミング環境を整える

新しいPCでもプログラミングできるように、色々と環境を整えました。とりあえず学校の授業でもよく使う、CとJavaを使えるようにします。

Chocolatey

Macでいうhomebrewのようなパッケージ管理ソフトがあると聞いて入れました。それがこのChocolatey。
サイトを見てみると、かなりのソフトをインストールできるみたいですね。
chocolatey.org

インストール

インストールはとても簡単です。まず、コマンドプロンプトまたはPowerShellを管理者権限で起動します。つぎにChocolateyのサイトに行き、コマンドプロンプト or PowerShellに適したコマンドをコピーしてきます。それを貼り付けて実行すれば、あとはインストールされるのを待つだけです。
f:id:Pano:20181205235224p:plain

よく使う機能

パッケージの検索

choco list

インストール済みパッケージの検索

choco list -localonly

パッケージのインストール

choco install <パッケージ名>

パッケージのアップデード

choco update <パッケージ名>

パッケージのアンインストール

choco uninstall <パッケージ名>

Git

Bash環境としてGitを使いたいので、インストールします。
Git
公式サイトからダウンロードしてきてもいいですが、今回は管理者権限のコマンドプロンプトでchocolateyを用いてインストールします。

choco install -y git

PATHの設定

bash.exeのあるディレクトリをPATHに追加します。(C:\Program Files\Git\binかな?)

Cmder

コンソールとしてCmderをインストールします。chocolateyでもインストールできますが、最新版を使いたい場合は公式サイトからインストールするのがいいでしょう。
cmder.net

PATHの設定

cmder.exeのあるディレクトリをPATHに追加します。

Cmderの設定

Cmderをより便利にするために色々と設定していきます。

日本語文字がつぶれて表示されるのを防ぐために、General>fontsにある"Monospace"のチェックを外します。
f:id:Pano:20181206005443p:plain

新しいタブを作るときに指定できるタスクを増やします。Startup>Tasksにある"Add/refuresh..."をクリックすると、いい感じの設定が自動で作成されます。ここでは、追加されたGit bashを選択して、default taskにしましょう。右下のウィンドウでは赤線で引いた形でタスク開始時のディレクトリを指定できます。この例ではホームディレクトリにしてあります。
f:id:Pano:20181206005820p:plain

ついでにStartupのSpecified named taskもGit bashにしておきましょう。
f:id:Pano:20181206010235p:plain

General>Tab barにある"Tab bar on bottom"のチェックを外すことで、タブバーをウィンドウ上部にもってきます。こっちの方がMacに近くて個人的にすきです。
f:id:Pano:20181206010333p:plain

カラースキームは"Twilight"にしました。自分の好みになるようにいじることもできます。
f:id:Pano:20181206010531p:plain

Features>Transparencyで、ウィンドウの透明度を設定できます。
f:id:Pano:20181206010651p:plain

Key & Macro>Keyboardの中にある、"[Inselection] Copy: Current selection as plain text: Copy(0,0)"の"Choose hotkey"を"なし"にします。こうすることで、「Ctrl」+「c」で割り込みシグナルを送ることができるようになります。
f:id:Pano:20181206010814p:plain

.bash_profileの設定

ホームディレクトリに追加される.bash_profileに日本語環境で使うためのLANG環境変数を追記します。

# generated by Git for Windows
test -f ~/.profile && . ~/.profile
test -f ~/.bashrc && . ~/.bashrc

export LANG=ja_JP.UTF-8

.bashrcの設定

エイリアスの設定をします。

alias ls='ls --show-control-chars -F --color --ignore={NTUSER.*,ntuser.*}'
alias activate='source activate'
alias deactivate='source deactivate'

日本語のファイル名やディレクトリ名が表示されるようにしました。また、ホームディレクトリにNTUSER.DAT{.....という長い名前の隠しファイルがあり、lsを実行するたびに表示されると邪魔なので表示されないようにしました。
Conda環境を切り替えるのに、bashだとsource activate [環境名]と打つ必要があります。面倒なのでactivate [環境名]だけで実行できるようにしました。deactivateも同様です。

MinGW

C言語等をコンパイルできるようにするために、GNUツールチェインであるMinGWをインストールします。

choco install mingw
gcc --version

としてバージョン情報が表示されれば無事インストールされています。

JDK8

Javaを使えるようにするためにJDK8をインストールします。

choco install jdk8
java -version

としてバージョン情報が表示されれば無事インストールされています。

その他

テキストエディタなどは好きなものを入れればいいでしょう。僕はとりあえずVisual Studio Codeを入れてみました。
CygwinやらMSYS2やらいろいろあってどうしようか迷いましたが、とりあえずこんな感じで構築してみました。しばらくこれで使ってみようと思います。

PCを組んでみた

導入

・今使ってるMBAがMid 2011モデルで古すぎる
・お金が貯まった
・色々やりたいゲームがある
・PC組んでみたい

ということで、初めて自作PCに挑戦してみました。

構成はこちら。Amazonで全部買えそう&色々なところで買うのも面倒だったので、パーツは全部Amazonで注文しました。
(※価格は購入時のものです 2018年11月ごろ)

CPU Intel Core i5 9600K BOX
¥34,980
CPUクーラー サイズ 無限五 リビジョンB SCMG-5100
¥5,038
メモリ Corsair CMK16GX4M2A2666C16 [DDR4 PC4-21300 8GB 2枚組]
¥18,435
マザーボード ASUS TUF H370-PRO GAMING
¥12,841
ビデオカード ASUS TURBO-GTX1070Ti-8G
¥50,998
SSD Intel SSD 760p SSDPEKKW256GBXT
¥8,478
HDD SEAGATE ST4000DM004 [4TB SATA600 7200]
¥9,117
ケース Thermaltake Versa H26 Black /w casefan CA-1 J5-00M1WN-01 [ブラック]
¥4,300
電源 玄人志向 KRPW-N600W/85+
¥5,378
合計
¥149,565

現在の価格はこちら
niku.webcrow.jp

これに加えて、今までデスクトップPCがなかったので以下のものも購入。

モニタ Dell SE2416H 23.8インチ/フルHD・IPS非光沢/6ms/VGA,HDMI
¥12,360
キーボード Logicool K120
¥811
マウス Logicool G300s
¥1,809
LANケーブル UGREEN カテゴリー7 RJ45
¥1,098

マウスはゲームもやるのでボタンがいくつかついてるやつ、キーボードはとりあえずあればいいので安いやつを選びました。

組み立て

11月某日
全パーツが到着。なんか浮かれてたので並べて撮ってみました。
f:id:Pano:20181125180516j:plain

マザボ開封
f:id:Pano:20181125180903j:plain

CPUを載せていきます。ここでミスったら壊れるという話を聞いていたので、緊張しながら載せました。
f:id:Pano:20181125181017j:plain

グリスを塗って
f:id:Pano:20181125181109j:plain


CPUクーラーを載せるためのマウンティングプレートが内外逆でした。
プレートを付けなおしてCPUクーラーを載せます。
f:id:Pano:20181125181544j:plain

メモリを開封
f:id:Pano:20181125181608j:plain

マザボのA1とA2スロットに差していきます。メーカーや型番によって並び順が違うみたいなのでちょっと戸惑いました。
f:id:Pano:20181125181812j:plain

マザボSSDを装着
f:id:Pano:20181125181912j:plain

あとはマザボGPUをつけて、ケースのファンや電源やHDDなどの配線をして、モニタ、キーボード、マウスを接続して完成です。(夢中になってたので写真が残ってなかった)

電源を入れると無事起動しました。
f:id:Pano:20181125182222j:plain

メモリ、SSD、HDDも認識され、ファンも問題なく動いています。
f:id:Pano:20181125182313j:plain

OSのインストール

OSはWindows10 Educationを選択。大学の関係で無料でインストールできるものがあったので、それを使います。
手元にWindows8.1の入ったノートPCがあったので、メディア作成ツールを用いてインストールするためのUSBメモリを作りました。
https://www.microsoft.com/ja-jp/software-download/windows10

Windows10無事起動
f:id:Pano:20181125183126j:plain

あとはデバイスマネージャーを開いて足りてないものをネットから拾ってきます。

諸々の設定を終えて試しにMinecraftを動かしてみたところ、なかなかのfpsが出ています。
f:id:Pano:20181125183505p:plain

これで快適なゲームライフをおくれそうです。

久々の更新

長いこと放置していて半分くらい存在を忘れていましたが、なんとか思い出したので、思い出した記念に更新してみました。

これからもぼちぼち気が向いたら更新していこうと思います。

 

写真はこないだ東京駅で食べたおいしい親子丼です。

f:id:Pano:20181125045112j:plain