{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# WEEK 04 MISSION: 나만의 리듬 그리드 만들기\n",
        "\n",
        "색상 리스트와 중첩 반복을 이용해 20개 이상의 원이 있는 리듬 그리드를 만듭니다. **팔레트 색상 2개 이상**, **열 수**, **크기 변화량**을 수정한 뒤 마지막 자동 검사에서 `WEEK 04 RHYTHM GRID COMPLETE`를 확인합니다. `.ipynb`와 `.png` 두 파일을 제출하면 남은 시간과 관계없이 귀가할 수 있습니다.\n",
        "\n",
        "빈 노트북에서 전체 코드를 다시 작성하지 않습니다. `EDIT`라고 표시된 값만 바꾸고, 중첩 반복과 자동 검사 영역은 그대로 둡니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# STEP 0: 준비 셀입니다. 이 셀은 수정하지 말고 실행만 하세요.\n",
        "from pathlib import Path\n",
        "from PIL import Image, ImageDraw\n",
        "from IPython.display import display\n",
        "\n",
        "canvas_width = 1000\n",
        "canvas_height = 760\n",
        "background_color = (245, 240, 228)\n",
        "outline_color = (34, 50, 60)\n",
        "\n",
        "STARTER_PALETTE = [\n",
        "    (36, 63, 80),\n",
        "    (111, 143, 126),\n",
        "    (211, 155, 42),\n",
        "    (201, 111, 93),\n",
        "]\n",
        "mission_step0_execution = get_ipython().execution_count\n",
        "mission_stage = 0\n",
        "print(\"✅ STEP 0 준비 완료: 다음 셀로 이동하세요.\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## STEP 1: 제출 정보와 리듬 의도 작성\n",
        "\n",
        "`=` 오른쪽의 네 문자열만 수정합니다. `rhythm_intent`에는 색과 크기가 어느 방향으로 어떻게 반복되는지 한 문장으로 작성합니다. 파일명은 `week04_학번_이름.png` 형식을 유지합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# STEP 1 / EDIT: 따옴표 안의 내용만 자신의 정보로 바꾸세요.\n",
        "assert mission_stage in (0, 1, 2, 3, 4), \"STEP 0 준비 셀을 먼저 실행하세요.\"\n",
        "student_id = \"학번\"\n",
        "student_name = \"이름 또는 별명\"\n",
        "rhythm_intent = \"색과 크기가 어느 방향으로 어떻게 반복되는지 한 문장으로 작성하세요.\"\n",
        "output_filename = \"week04_학번_이름.png\"\n",
        "\n",
        "# DO NOT EDIT: 아래 실행 확인 코드는 수정하지 마세요.\n",
        "mission_step1_execution = get_ipython().execution_count\n",
        "mission_stage = 1\n",
        "print(\"리듬 의도:\", rhythm_intent)\n",
        "print(\"저장 파일명:\", output_filename)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## STEP 2: 색상 리스트 수정\n",
        "\n",
        "처음에는 기준값 그대로 실행해 네 색의 순서를 확인합니다. 그다음 `palette`에서 **RGB 튜플 두 개 이상**을 수정합니다. 리스트의 대괄호와 각 색의 소괄호는 유지하고, 모든 RGB 채널은 0부터 255 사이의 정수로 작성합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# STEP 2 / EDIT: 아래 리스트에서 RGB 튜플 두 개 이상을 수정하세요.\n",
        "assert mission_stage in (1, 2, 3, 4), \"STEP 1 제출 정보 셀을 먼저 실행하세요.\"\n",
        "palette = [\n",
        "    (36, 63, 80),\n",
        "    (111, 143, 126),\n",
        "    (211, 155, 42),\n",
        "    (201, 111, 93),\n",
        "]\n",
        "\n",
        "# DO NOT EDIT: 아래 코드는 리스트와 RGB 범위를 확인하고 색 순서를 보여줍니다.\n",
        "assert type(palette) is list, \"palette는 대괄호로 만든 리스트여야 합니다.\"\n",
        "assert 4 <= len(palette) <= 6, \"palette에는 4개 이상 6개 이하의 색을 넣으세요.\"\n",
        "assert all(type(color) is tuple and len(color) == 3 for color in palette), \"각 색을 (R, G, B) 튜플로 작성하세요.\"\n",
        "assert all(type(channel) is int and 0 <= channel <= 255 for color in palette for channel in color), \"RGB 값은 따옴표 없는 0~255 정수여야 합니다.\"\n",
        "assert len(set(palette)) >= 3, \"서로 다른 색을 세 개 이상 사용하세요.\"\n",
        "assert background_color not in palette, \"도형 색은 배경색과 다르게 정하세요.\"\n",
        "\n",
        "palette_preview = Image.new(\"RGB\", (len(palette) * 120, 120), background_color)\n",
        "palette_draw = ImageDraw.Draw(palette_preview)\n",
        "for color_index in range(len(palette)):\n",
        "    left = color_index * 120\n",
        "    palette_draw.rectangle((left, 0, left + 119, 119), fill=palette[color_index])\n",
        "display(palette_preview)\n",
        "\n",
        "changed_palette_count = sum(\n",
        "    index >= len(STARTER_PALETTE) or color != STARTER_PALETTE[index]\n",
        "    for index, color in enumerate(palette)\n",
        ")\n",
        "print(\"현재 변경한 팔레트 색상:\", changed_palette_count, \"개 / 완료 조건은 2개 이상\")\n",
        "mission_step2_execution = get_ipython().execution_count\n",
        "mission_stage = 2"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## STEP 3: 열 수와 크기 변화량 수정\n",
        "\n",
        "먼저 `columns = 5`, `size_step = 0`인 기준 격자를 실행합니다. 그다음 열 수를 6으로 바꾸고, 크기 변화량을 4부터 10 사이의 정수로 정합니다. 행 수는 팔레트의 색 개수로 자동 결정됩니다. 중첩 반복 코드는 수정하지 않습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# STEP 3 / EDIT: columns와 size_step의 오른쪽 숫자만 수정하세요.\n",
        "assert mission_stage in (2, 3, 4), \"STEP 2 팔레트 셀을 먼저 실행하세요.\"\n",
        "columns = 5\n",
        "size_step = 0\n",
        "\n",
        "# DO NOT EDIT: 아래 코드는 행과 열을 반복해 리듬 그리드를 만듭니다.\n",
        "rows = len(palette)\n",
        "start_x = 70\n",
        "start_y = 60\n",
        "gap_x = 150\n",
        "gap_y = 110\n",
        "base_size = 44\n",
        "\n",
        "assert type(columns) is int and 5 <= columns <= 6, \"columns는 5 또는 6으로 작성하세요.\"\n",
        "assert type(size_step) is int and 0 <= size_step <= 10, \"size_step은 0부터 10 사이의 정수로 작성하세요.\"\n",
        "largest_size = base_size + (columns - 1) * size_step\n",
        "last_x = start_x + (columns - 1) * gap_x\n",
        "last_y = start_y + (rows - 1) * gap_y\n",
        "assert last_x + largest_size < canvas_width, \"마지막 열이 캔버스 밖으로 나갑니다.\"\n",
        "assert last_y + largest_size < canvas_height, \"마지막 행이 캔버스 밖으로 나갑니다.\"\n",
        "\n",
        "image = Image.new(\"RGB\", (canvas_width, canvas_height), background_color)\n",
        "draw = ImageDraw.Draw(image)\n",
        "drawn_shape_count = 0\n",
        "visited_cells = []\n",
        "\n",
        "for row in range(rows):\n",
        "    color = palette[row]\n",
        "\n",
        "    for column in range(columns):\n",
        "        x = start_x + column * gap_x\n",
        "        y = start_y + row * gap_y\n",
        "        size = base_size + column * size_step\n",
        "        draw.ellipse(\n",
        "            (x, y, x + size, y + size),\n",
        "            fill=color,\n",
        "            outline=outline_color,\n",
        "            width=3,\n",
        "        )\n",
        "        visited_cells.append((row, column))\n",
        "        drawn_shape_count += 1\n",
        "\n",
        "display(image)\n",
        "print(\"격자:\", rows, \"행 ×\", columns, \"열\")\n",
        "print(\"그린 원:\", drawn_shape_count, \"개\")\n",
        "print(\"열이 바뀔 때 늘어나는 크기:\", size_step, \"픽셀\")\n",
        "mission_step3_execution = get_ipython().execution_count\n",
        "mission_stage = 3"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## STEP 4: PNG 저장\n",
        "\n",
        "색과 크기의 반복이 `rhythm_intent`와 연결되는지 확인한 뒤 아래 셀을 실행합니다. 팔레트나 격자 값을 다시 바꾸었다면 STEP 2와 STEP 3을 다시 실행한 뒤 저장 셀도 다시 실행합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# STEP 4: 최종 리듬 그리드를 PNG로 저장합니다. 이 셀은 수정하지 마세요.\n",
        "assert mission_stage == 3, \"STEP 3 리듬 그리드 셀을 먼저 실행하세요.\"\n",
        "mission_step4_execution = get_ipython().execution_count\n",
        "image.save(output_filename)\n",
        "with Image.open(output_filename) as opened_image:\n",
        "    saved_image = opened_image.copy()\n",
        "display(saved_image)\n",
        "print(\"✅ PNG 저장 완료:\", output_filename)\n",
        "mission_stage = 4"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## FINAL CHECK: 전체 재실행 후 완료 증명\n",
        "\n",
        "작품을 완성했다면 **런타임 → 세션 다시 시작**을 선택한 뒤 **런타임 → 모두 실행**을 선택합니다. 마지막 셀에 `WEEK 04 RHYTHM GRID COMPLETE`가 나타나면 `.ipynb`와 `.png` 두 파일을 제출하고 바로 귀가할 수 있습니다.\n",
        "\n",
        "자동 검사는 미적 취향을 채점하지 않습니다. 색상 리스트, 반복 횟수, 두 시각 속성의 변화, PNG 저장, 전체 실행 순서만 확인합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# FINAL CHECK: 이 셀의 코드는 수정하지 마세요.\n",
        "mission_final_execution = get_ipython().execution_count\n",
        "\n",
        "assert type(student_id) is str and student_id.strip() not in (\"\", \"학번\"), \"STEP 1의 student_id를 자신의 학번으로 바꾸세요.\"\n",
        "assert type(student_name) is str and student_name.strip() not in (\"\", \"이름 또는 별명\"), \"STEP 1의 student_name을 이름 또는 별명으로 바꾸세요.\"\n",
        "assert type(rhythm_intent) is str and len(rhythm_intent.strip()) >= 15 and rhythm_intent != \"색과 크기가 어느 방향으로 어떻게 반복되는지 한 문장으로 작성하세요.\", \"rhythm_intent에 색과 크기의 반복 의도를 15자 이상 작성하세요.\"\n",
        "expected_output_filename = \"week04_\" + student_id + \"_\" + student_name + \".png\"\n",
        "assert output_filename == expected_output_filename, \"output_filename을 \" + expected_output_filename + \"으로 작성하세요.\"\n",
        "\n",
        "assert type(palette) is list and 4 <= len(palette) <= 6, \"palette를 4~6개의 RGB 튜플이 있는 리스트로 작성하세요.\"\n",
        "assert all(type(color) is tuple and len(color) == 3 for color in palette), \"각 색을 (R, G, B) 튜플로 작성하세요.\"\n",
        "assert all(type(channel) is int and 0 <= channel <= 255 for color in palette for channel in color), \"모든 RGB 채널은 0~255 정수여야 합니다.\"\n",
        "assert len(set(palette)) >= 3, \"서로 다른 색을 세 개 이상 사용하세요.\"\n",
        "changed_palette_count = sum(index >= len(STARTER_PALETTE) or color != STARTER_PALETTE[index] for index, color in enumerate(palette))\n",
        "assert changed_palette_count >= 2, \"기준 팔레트에서 색상 두 개 이상을 변경하세요.\"\n",
        "\n",
        "assert rows == len(palette), \"행 수는 팔레트의 색상 수와 같아야 합니다.\"\n",
        "assert columns == 6, \"columns를 6으로 바꾸어 기준 격자보다 한 열 늘리세요.\"\n",
        "assert rows * columns >= 20, \"행 × 열이 20 이상이 되도록 팔레트와 열 수를 확인하세요.\"\n",
        "assert 4 <= size_step <= 10, \"size_step을 4부터 10 사이로 바꾸어 열마다 크기가 달라지게 하세요.\"\n",
        "assert drawn_shape_count == rows * columns, \"모든 행과 열에 원이 하나씩 그려졌는지 확인하세요.\"\n",
        "expected_cells = [(row, column) for row in range(rows) for column in range(columns)]\n",
        "assert visited_cells == expected_cells, \"중첩 반복 코드가 모든 격자 칸을 순서대로 방문해야 합니다.\"\n",
        "assert isinstance(image, Image.Image) and image.mode == \"RGB\" and image.size == (1000, 760), \"1000 × 760 RGB 이미지를 다시 생성하세요.\"\n",
        "visible_color_counts = image.getcolors(maxcolors=canvas_width * canvas_height)\n",
        "assert visible_color_counts is not None, \"이미지의 색상 정보를 확인할 수 없습니다.\"\n",
        "visible_colors = {color for count, color in visible_color_counts}\n",
        "assert background_color in visible_colors and outline_color in visible_colors, \"배경색과 윤곽색이 모두 보여야 합니다.\"\n",
        "assert all(color in visible_colors for color in palette), \"팔레트의 모든 색이 한 행 이상에서 보이도록 하세요.\"\n",
        "\n",
        "assert Path(output_filename).is_file(), \"STEP 4 저장 셀을 실행해 PNG 파일을 만드세요.\"\n",
        "with Image.open(output_filename) as checked_file:\n",
        "    assert checked_file.format == \"PNG\", \"파일 확장자와 저장 형식을 PNG로 사용하세요.\"\n",
        "    assert checked_file.mode == \"RGB\" and checked_file.size == image.size, \"저장된 PNG의 모드와 크기를 확인하세요.\"\n",
        "    assert checked_file.tobytes() == image.tobytes(), \"이미지를 수정한 뒤 STEP 4 저장 셀을 다시 실행하세요.\"\n",
        "\n",
        "assert mission_stage == 4, \"STEP 0부터 STEP 4까지 순서대로 실행하세요.\"\n",
        "assert (mission_step0_execution, mission_step1_execution, mission_step2_execution, mission_step3_execution, mission_step4_execution, mission_final_execution) == (1, 2, 3, 4, 5, 6), \"세션을 다시 시작한 뒤 위에서 아래로 모두 실행하세요.\"\n",
        "\n",
        "print(\"✅ 제출 정보와 리듬 의도 검사 통과\")\n",
        "print(\"✅ 색상 리스트와 팔레트 변경 검사 통과\")\n",
        "print(\"✅ 중첩 반복과 20개 이상 도형 검사 통과\")\n",
        "print(\"✅ 색과 크기의 두 속성 변화 검사 통과\")\n",
        "print(\"✅ PNG 저장과 새 런타임 전체 실행 검사 통과\")\n",
        "print(\"🎉 WEEK 04 RHYTHM GRID COMPLETE\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 선택 확장\n",
        "\n",
        "필수 미션을 완료한 뒤 더 실험하고 싶은 경우에만 진행합니다. 추가 점수는 없습니다.\n",
        "\n",
        "- `size_step`을 4, 7, 10으로 각각 바꾸어 크기 변화의 강도를 비교합니다.\n",
        "- 팔레트에 한두 색을 추가해 5행 또는 6행의 리듬을 비교합니다.\n",
        "- 팔레트 순서만 바꾸어 행의 색 흐름을 비교합니다."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "provenance": []
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
