{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# WEEK 03 MISSION: 좌표와 색상의 적용\n",
        "\n",
        "1교시의 기준점·중심·여백과 2교시의 RGB·경계 상자·그리기 순서를 활용해 기하학적 이미지를 완성합니다. **기준 실행 → 설계 기준 → 위치와 크기 → 색상 조정 → 겹침 비교 → 저장**의 순서입니다. 웹 안내와 이 노트북의 STEP 번호는 같습니다.\n",
        "\n",
        "전체 코드를 외워 작성하지 않습니다. `EDIT` 영역에서 값과 최종 이미지 선택만 수정하고, `DO NOT EDIT` 영역은 그대로 둡니다. 한 속성씩 변경한 뒤 다시 실행해 결과를 비교합니다. 평행 이동에서는 두 모서리의 여러 좌표가 함께 바뀔 수 있습니다.\n",
        "\n",
        "완료 조건은 자동 검사 PASS, 변경 기록 네 항목 작성, 실행 결과가 포함된 `.ipynb`와 최종 `.png` 제출입니다. 자동 검사는 기술적 조건만 확인하며 설명의 타당성과 미적 취향을 채점하지 않습니다. 제출 확인 후 남은 시간과 관계없이 퇴실할 수 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## STEP 0 · 기준 이미지\n",
        "\n",
        "아래 셀을 수정 없이 실행하면 2교시의 기준 이미지가 표시됩니다. **600 × 400**, 왼쪽 위 원점, 사각형 → 타원 → 삼각형의 순서를 확인합니다. 아직 STEP 1 이후의 값을 바꾸거나 마지막 검사까지 실행할 필요는 없습니다.\n",
        "\n",
        "설정·검사 코드는 제공된 도구입니다. 낯선 조건문이나 반복문은 해석할 필요가 없습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# STEP 0 / DO NOT EDIT: 준비와 기준 이미지\n",
        "from pathlib import Path\n",
        "from PIL import Image, ImageDraw\n",
        "from IPython.display import display\n",
        "\n",
        "canvas_width = 600\n",
        "canvas_height = 400\n",
        "STARTER_BACKGROUND_COLOR = (244, 239, 229)\n",
        "STARTER_PRIMARY_COLOR = (235, 86, 75)\n",
        "STARTER_SECONDARY_COLOR = (48, 95, 180)\n",
        "STARTER_ACCENT_COLOR = (244, 190, 55)\n",
        "STARTER_OUTLINE_COLOR = (28, 31, 38)\n",
        "STARTER_RECTANGLE_BOX = (50, 60, 330, 240)\n",
        "STARTER_ELLIPSE_BOX = (230, 100, 510, 360)\n",
        "STARTER_TRIANGLE_POINTS = ((390, 55), (560, 220), (340, 220))\n",
        "\n",
        "background_color = (244, 239, 229)\n",
        "primary_color = (235, 86, 75)\n",
        "secondary_color = (48, 95, 180)\n",
        "accent_color = (244, 190, 55)\n",
        "outline_color = (28, 31, 38)\n",
        "rectangle_box = STARTER_RECTANGLE_BOX\n",
        "ellipse_box = STARTER_ELLIPSE_BOX\n",
        "triangle_points = STARTER_TRIANGLE_POINTS\n",
        "\n",
        "image = Image.new(\"RGB\", (canvas_width, canvas_height), background_color)\n",
        "draw = ImageDraw.Draw(image)\n",
        "draw.rectangle(rectangle_box, fill=primary_color, outline=outline_color, width=4)\n",
        "draw.ellipse(ellipse_box, fill=secondary_color, outline=outline_color, width=4)\n",
        "draw.polygon(triangle_points, fill=accent_color, outline=outline_color, width=4)\n",
        "draw.line(((40, 340), (560, 340)), fill=outline_color, width=6)\n",
        "display(image)\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",
        "1교시에서 정한 **기준점**을 사각형의 왼쪽 위 위치로 사용합니다. 그 점과 배치 의도는 아래 변경 기록에도 남깁니다. `anchor_x`와 `anchor_y`에 그 점의 두 좌표를 입력합니다. 기본값 (50, 60)은 기준 이미지의 위치이므로 다른 위치로 바꿉니다.\n",
        "\n",
        "도형 전체가 **안전 영역 x=40~560, y=40~360**에 들어가야 합니다. 기준 사각형의 두 경계 차이는 가로 280, 세로 180이므로 `anchor_x + 280`, `anchor_y + 180`까지 확인합니다. 점만 영역 안에 있다고 도형 전체가 들어가는 것은 아닙니다.\n",
        "\n",
        "예를 들어 기준점 (150, 200)을 그대로 사용하면 아래쪽 경계가 380이 됩니다. 기준점의 y를 180 이하로 조정하고 그 이유를 기록합니다. 이 단계에서는 크기를 줄이지 않고 위치만 조정합니다.\n",
        "\n",
        "학번·이름·표현 의도·파일명은 문자열로 작성합니다. `design_intent`에는 먼저 보이게 할 도형과 이유를 한 문장으로 적습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# STEP 1 / EDIT: 제출 정보와 설계 기준점\n",
        "student_id = \"학번\"\n",
        "student_name = \"이름 또는 별명\"\n",
        "design_intent = \"표현 의도\"\n",
        "output_filename = \"week03_학번_이름.png\"\n",
        "anchor_x = 50\n",
        "anchor_y = 60\n",
        "\n",
        "# DO NOT EDIT: 입력과 실행 순서 확인\n",
        "assert \"mission_stage\" in globals() and mission_stage in (0, 1, 2, 3, 4, 5), \"STEP 0 기준 이미지 셀을 먼저 실행하세요.\"\n",
        "assert type(output_filename) is str and output_filename.endswith(\".png\"), \"파일명은 따옴표 안에 .png로 끝나도록 작성하세요.\"\n",
        "assert type(anchor_x) is int and type(anchor_y) is int, \"기준점은 따옴표 없는 정수 좌표로 작성하세요.\"\n",
        "print(\"사각형의 왼쪽 위 기준점:\", anchor_x, anchor_y)\n",
        "print(\"예상 오른쪽 아래:\", anchor_x + 280, anchor_y + 180)\n",
        "print(\"표현 의도:\", design_intent)\n",
        "mission_step1_execution = get_ipython().execution_count\n",
        "mission_stage = 1"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## STEP 2 · 위치와 크기\n",
        "\n",
        "**먼저 사각형 이동만 확인합니다.** STEP 1의 기준점이 아래 경계 상자에 연결됩니다. 오른쪽과 아래쪽 경계를 같은 기준점으로 계산하므로 가로·세로 차이 280·180이 유지됩니다. 280과 180은 이 이동 실습에서 변경하지 않습니다.\n",
        "\n",
        "그다음 **타원의 크기 또는 비율을 변경합니다.** `ellipse_box`의 경계값 하나를 조정하고 이 셀 전체를 다시 실행합니다. 예를 들어 아래쪽 경계 360을 320으로 바꾸면 가로 차이 280은 유지되고 세로 차이는 260에서 220으로 줄어듭니다.\n",
        "\n",
        "사각형·타원의 가로와 세로 경계 차이는 각각 60 이상으로 유지합니다. 모든 도형을 안전 영역 안에 두고, 세 내부색이 보이도록 배치합니다. 삼각형 좌표는 기본값을 유지해도 됩니다.\n",
        "\n",
        "위치만 옮길 때는 두 모서리를 함께 바꾸고, 크기를 바꿀 때는 두 경계 사이의 차이를 바꿉니다. 두 작업을 마치면 사각형과 타원, **도형 좌표 묶음 두 개**가 기준값과 달라집니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# STEP 2 / EDIT: 기준점 연결과 타원의 크기\n",
        "rectangle_box = (anchor_x, anchor_y, anchor_x + 280, anchor_y + 180)\n",
        "ellipse_box = (230, 100, 510, 360)\n",
        "triangle_points = (\n",
        "    (390, 55),\n",
        "    (560, 220),\n",
        "    (340, 220)\n",
        ")\n",
        "\n",
        "# DO NOT EDIT: 좌표 확인과 이미지 재생성\n",
        "assert \"mission_stage\" in globals() and mission_stage in (1, 2, 3, 4, 5), \"STEP 1 설계 기준 셀을 먼저 실행하세요.\"\n",
        "for box in (rectangle_box, ellipse_box):\n",
        "    assert type(box) is tuple and len(box) == 4 and all(type(value) is int for value in box), \"경계 상자는 정수 네 개의 튜플이어야 합니다.\"\n",
        "    left, top, right, bottom = box\n",
        "    assert 40 <= left < right <= 560 and 40 <= top < bottom <= 360, \"도형 전체를 안전 영역 x=40~560, y=40~360 안에 배치하세요. STEP 1 기준점과 STEP 2 경계를 확인하세요.\"\n",
        "    assert right - left >= 60 and bottom - top >= 60, \"사각형과 타원의 가로·세로 경계 차이를 각각 60 이상으로 유지하세요.\"\n",
        "assert type(triangle_points) is tuple and len(triangle_points) == 3, \"삼각형에는 점 세 개가 필요합니다.\"\n",
        "assert all(type(point) is tuple and len(point) == 2 and all(type(value) is int for value in point) for point in triangle_points), \"각 점을 (x, y) 정수 튜플로 작성하세요.\"\n",
        "assert all(40 <= x <= 560 and 40 <= y <= 360 for x, y in triangle_points), \"삼각형의 모든 점을 안전 영역 안에 배치하세요.\"\n",
        "a, b, c = triangle_points\n",
        "triangle_area_twice = abs(a[0] * (b[1] - c[1]) + b[0] * (c[1] - a[1]) + c[0] * (a[1] - b[1]))\n",
        "assert triangle_area_twice > 0, \"삼각형의 세 점을 한 직선 위에 두지 않도록 배치하세요.\"\n",
        "\n",
        "image = Image.new(\"RGB\", (canvas_width, canvas_height), background_color)\n",
        "draw = ImageDraw.Draw(image)\n",
        "draw.rectangle(rectangle_box, fill=primary_color, outline=outline_color, width=4)\n",
        "draw.ellipse(ellipse_box, fill=secondary_color, outline=outline_color, width=4)\n",
        "draw.polygon(triangle_points, fill=accent_color, outline=outline_color, width=4)\n",
        "draw.line(((40, 340), (560, 340)), fill=outline_color, width=6)\n",
        "display(image)\n",
        "current_geometry = (rectangle_box, ellipse_box, triangle_points)\n",
        "starter_geometry = (STARTER_RECTANGLE_BOX, STARTER_ELLIPSE_BOX, STARTER_TRIANGLE_POINTS)\n",
        "changed_geometry_count = sum(current != starter for current, starter in zip(current_geometry, starter_geometry))\n",
        "print(\"현재 변경한 도형 좌표 묶음:\", changed_geometry_count, \"개\")\n",
        "mission_step2_execution = get_ipython().execution_count\n",
        "mission_stage = 2"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## STEP 3 · 색상 조정\n",
        "\n",
        "좌표를 고정하고 **색상 역할을 최소 두 개** 변경합니다. 한 RGB 채널을 먼저 바꾸고 셀 전체를 실행해 팔레트와 이미지가 함께 바뀌는지 확인한 뒤 다음 색상을 조정합니다.\n",
        "\n",
        "다섯 역할에는 서로 다른 RGB 튜플을 사용하고 각 채널은 0~255 정수로 작성합니다. 이미 표시된 출력은 변수만 수정한다고 갱신되지 않습니다. 이 셀은 새 이미지와 그리기 도구를 생성한 뒤 같은 좌표로 다시 그립니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# STEP 3 / EDIT: 다섯 역할 중 최소 두 색상 조정\n",
        "background_color = (244, 239, 229)\n",
        "primary_color = (235, 86, 75)\n",
        "secondary_color = (48, 95, 180)\n",
        "accent_color = (244, 190, 55)\n",
        "outline_color = (28, 31, 38)\n",
        "\n",
        "# DO NOT EDIT: RGB 검사, 팔레트와 이미지 재생성\n",
        "assert \"mission_stage\" in globals() and mission_stage in (2, 3, 4, 5), \"STEP 2 위치와 크기 셀을 먼저 실행하세요.\"\n",
        "current_palette = (background_color, primary_color, secondary_color, accent_color, outline_color)\n",
        "assert all(type(color) is tuple and len(color) == 3 for color in current_palette), \"모든 색을 (R, G, B) 세 정수로 작성하세요.\"\n",
        "assert all(type(channel) is int and 0 <= channel <= 255 for color in current_palette for channel in color), \"RGB 값은 따옴표 없는 0~255 정수여야 합니다.\"\n",
        "palette_preview = Image.new(\"RGB\", (500, 100), background_color)\n",
        "palette_draw = ImageDraw.Draw(palette_preview)\n",
        "palette_draw.rectangle((100, 0, 199, 99), fill=primary_color)\n",
        "palette_draw.rectangle((200, 0, 299, 99), fill=secondary_color)\n",
        "palette_draw.rectangle((300, 0, 399, 99), fill=accent_color)\n",
        "palette_draw.rectangle((400, 0, 499, 99), fill=outline_color)\n",
        "display(palette_preview)\n",
        "\n",
        "image = Image.new(\"RGB\", (canvas_width, canvas_height), background_color)\n",
        "draw = ImageDraw.Draw(image)\n",
        "draw.rectangle(rectangle_box, fill=primary_color, outline=outline_color, width=4)\n",
        "draw.ellipse(ellipse_box, fill=secondary_color, outline=outline_color, width=4)\n",
        "draw.polygon(triangle_points, fill=accent_color, outline=outline_color, width=4)\n",
        "draw.line(((40, 340), (560, 340)), fill=outline_color, width=6)\n",
        "display(image)\n",
        "starter_palette = (STARTER_BACKGROUND_COLOR, STARTER_PRIMARY_COLOR, STARTER_SECONDARY_COLOR, STARTER_ACCENT_COLOR, STARTER_OUTLINE_COLOR)\n",
        "changed_palette_count = sum(current != starter for current, starter in zip(current_palette, starter_palette))\n",
        "print(\"현재 변경한 색상 역할:\", changed_palette_count, \"개\")\n",
        "mission_step3_execution = get_ipython().execution_count\n",
        "mission_stage = 3"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## STEP 4 · 겹침 비교\n",
        "\n",
        "같은 좌표와 색상으로 **순서 A: 사각형 → 타원**, **순서 B: 타원 → 사각형**을 비교합니다. 두 코드는 아래 두 도형 명령의 순서만 다릅니다. 삼각형과 선은 양쪽에서 마지막에 그립니다.\n",
        "\n",
        "각 결과는 서로 다른 빈 이미지에서 시작합니다. 겹친 부분의 색이 어떻게 달라지는지 확인합니다. 차이가 없다면 STEP 2에서 사각형과 타원이 일부 겹치도록 배치하고 STEP 2 → 3 → 4를 다시 실행합니다. 삼각형이 겹친 부분 전체를 가리지 않도록 합니다.\n",
        "\n",
        "맨 아래 `image = order_a_image`는 저장할 최종 이미지를 선택하는 대입문입니다. B를 선택하려면 오른쪽만 `order_b_image`로 변경하고 이 셀 전체를 다시 실행합니다. 비교용 도형 명령과 검사 코드는 그대로 둡니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# STEP 4 / DO NOT EDIT: 동일 조건의 두 그리기 순서\n",
        "assert \"mission_stage\" in globals() and mission_stage in (3, 4, 5), \"STEP 3 색상 조정 셀을 먼저 실행하세요.\"\n",
        "\n",
        "# 순서 A: 사각형 다음 타원\n",
        "order_a_image = Image.new(\"RGB\", (canvas_width, canvas_height), background_color)\n",
        "order_a_draw = ImageDraw.Draw(order_a_image)\n",
        "order_a_draw.rectangle(rectangle_box, fill=primary_color, outline=outline_color, width=4)\n",
        "order_a_draw.ellipse(ellipse_box, fill=secondary_color, outline=outline_color, width=4)\n",
        "order_a_draw.polygon(triangle_points, fill=accent_color, outline=outline_color, width=4)\n",
        "order_a_draw.line(((40, 340), (560, 340)), fill=outline_color, width=6)\n",
        "print(\"순서 A: 사각형 → 타원\")\n",
        "display(order_a_image)\n",
        "\n",
        "# 순서 B: 타원 다음 사각형\n",
        "order_b_image = Image.new(\"RGB\", (canvas_width, canvas_height), background_color)\n",
        "order_b_draw = ImageDraw.Draw(order_b_image)\n",
        "order_b_draw.ellipse(ellipse_box, fill=secondary_color, outline=outline_color, width=4)\n",
        "order_b_draw.rectangle(rectangle_box, fill=primary_color, outline=outline_color, width=4)\n",
        "order_b_draw.polygon(triangle_points, fill=accent_color, outline=outline_color, width=4)\n",
        "order_b_draw.line(((40, 340), (560, 340)), fill=outline_color, width=6)\n",
        "print(\"순서 B: 타원 → 사각형\")\n",
        "display(order_b_image)\n",
        "\n",
        "# EDIT: 저장할 최종 이미지를 선택합니다.\n",
        "image = order_a_image\n",
        "\n",
        "# DO NOT EDIT: 비교 여부와 실행 단계 확인\n",
        "assert order_a_image.tobytes() != order_b_image.tobytes(), \"겹침 비교를 위해 사각형과 타원의 일부가 겹치도록 STEP 2에서 조정하세요.\"\n",
        "assert image is order_a_image or image is order_b_image, \"최종 이미지는 order_a_image 또는 order_b_image로 선택하세요.\"\n",
        "mission_step4_execution = get_ipython().execution_count\n",
        "mission_stage = 4\n",
        "print(\"최종 선택:\", \"A\" if image is order_a_image else \"B\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### 변경 기록\n",
        "\n",
        "이 텍스트 셀을 더블클릭해 표를 채웁니다. 새 코드나 별도 파일은 필요하지 않습니다. **변경 전 → 변경 후 → 결과 → 설계 의도와의 관계**를 짧게 적습니다. 자동 검사는 이 설명의 타당성을 판정하지 않으므로 제출 전 직접 확인합니다.\n",
        "\n",
        "| 항목 | 변경 전 | 변경 후 | 결과와 이유 |\n",
        "|---|---|---|---|\n",
        "| 기준점·위치 | 기준 사각형의 왼쪽 위 (50, 60) | 1교시 기준점과 조정한 좌표 | 이동 방향, 크기 유지 여부, 기준점을 조정한 이유 |\n",
        "| 타원의 크기 | 가로 차이 280, 세로 차이 260 | 변경한 경계와 가로·세로 차이 | 비율의 변화와 의도 |\n",
        "| 색상 | 변경한 두 역할의 원래 RGB | 변경한 두 역할의 새 RGB | 색상의 변화와 강조 대상 |\n",
        "| 겹침 순서 | A: 사각형 → 타원 | B: 타원 → 사각형, 최종 선택 A/B | 겹친 부분에 보이는 색과 선택 이유 |"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## STEP 5 · PNG 저장\n",
        "\n",
        "변경 기록을 작성하고 최종 선택을 확인한 뒤 저장 셀을 실행합니다. 선택한 `image`를 PNG로 저장하고 다시 열어 표시합니다.\n",
        "\n",
        "수정 후에는 **해당 STEP부터 STEP 5까지 순서대로** 다시 실행합니다. 기준점 수정은 STEP 1부터, 좌표 수정은 STEP 2부터, 색상 수정은 STEP 3부터, 최종 순서 선택은 STEP 4부터입니다. 화면 확인과 파일 저장은 별개의 단계입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# STEP 5 / DO NOT EDIT: 최종 선택 이미지 저장\n",
        "assert \"mission_stage\" in globals() and mission_stage == 4, \"STEP 4 겹침 비교와 최종 선택 셀을 먼저 실행하세요.\"\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_step5_execution = get_ipython().execution_count\n",
        "mission_stage = 5"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## FINAL CHECK · 검사와 제출\n",
        "\n",
        "값과 변경 기록을 모두 수정한 뒤 **런타임 → 세션 다시 시작 → 모두 실행**을 진행합니다. 아래 검사는 제출 정보, RGB 범위, 이동·크기 변경, 도형 가시성, 겹침 비교, 최신 PNG와 전체 실행 순서를 확인합니다. 기록의 설명과 미적 완성도는 자동 판정 대상이 아닙니다.\n",
        "\n",
        "`WEEK 03 IMAGE MISSION COMPLETE`를 확인하고, 변경 기록 네 항목과 실행 결과가 포함된 `.ipynb`, 최종 `.png`를 제출합니다. **자동 검사 PASS만으로 제출과 설명 확인이 끝나는 것은 아닙니다.**\n",
        "\n",
        "오류가 발생하면 마지막 한글 안내에 따라 EDIT 값을 고친 뒤 해당 단계부터 다시 실행합니다. 최종 검사 전에는 다시 세션을 시작하고 모두 실행합니다. 검사 코드는 삭제하거나 변경하지 않습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# FINAL CHECK / DO NOT EDIT: 기술적 완료 조건\n",
        "mission_final_execution = get_ipython().execution_count\n",
        "assert \"mission_stage\" in globals() and mission_stage == 5, \"STEP 0부터 STEP 5까지 순서대로 실행하세요.\"\n",
        "assert type(student_id) is str and student_id.strip() not in (\"\", \"학번\"), \"STEP 1에 학번을 작성하세요.\"\n",
        "assert type(student_name) is str and student_name.strip() not in (\"\", \"이름 또는 별명\"), \"STEP 1에 이름 또는 별명을 작성하세요.\"\n",
        "assert type(design_intent) is str and design_intent.strip() not in (\"\", \"표현 의도\"), \"STEP 1에 표현 의도를 한 문장으로 작성하세요.\"\n",
        "expected_output_filename = \"week03_\" + student_id + \"_\" + student_name + \".png\"\n",
        "assert output_filename == expected_output_filename, \"파일명을 \" + expected_output_filename + \"으로 작성하세요.\"\n",
        "\n",
        "current_palette = (background_color, primary_color, secondary_color, accent_color, outline_color)\n",
        "assert all(type(color) is tuple and len(color) == 3 for color in current_palette), \"모든 색을 (R, G, B) 세 정수로 작성하세요.\"\n",
        "assert all(type(channel) is int and 0 <= channel <= 255 for color in current_palette for channel in color), \"RGB 값은 따옴표 없는 0~255 정수여야 합니다.\"\n",
        "assert len(set(current_palette)) == 5, \"다섯 색상 역할에 서로 다른 색을 사용하세요.\"\n",
        "starter_palette = (STARTER_BACKGROUND_COLOR, STARTER_PRIMARY_COLOR, STARTER_SECONDARY_COLOR, STARTER_ACCENT_COLOR, STARTER_OUTLINE_COLOR)\n",
        "changed_palette_count = sum(current != starter for current, starter in zip(current_palette, starter_palette))\n",
        "assert changed_palette_count >= 2, \"색상 역할을 최소 두 개 변경하세요.\"\n",
        "current_geometry = (rectangle_box, ellipse_box, triangle_points)\n",
        "starter_geometry = (STARTER_RECTANGLE_BOX, STARTER_ELLIPSE_BOX, STARTER_TRIANGLE_POINTS)\n",
        "changed_geometry_count = sum(current != starter for current, starter in zip(current_geometry, starter_geometry))\n",
        "assert changed_geometry_count >= 2, \"도형 좌표 묶음을 최소 두 개 변경하세요.\"\n",
        "assert rectangle_box[:2] == (anchor_x, anchor_y) and rectangle_box[:2] != STARTER_RECTANGLE_BOX[:2], \"STEP 1의 기준점으로 사각형을 이동하세요.\"\n",
        "assert rectangle_box[2] - rectangle_box[0] == 280 and rectangle_box[3] - rectangle_box[1] == 180, \"사각형 이동에서는 기존 크기를 유지하세요.\"\n",
        "assert (ellipse_box[2] - ellipse_box[0], ellipse_box[3] - ellipse_box[1]) != (280, 260), \"타원의 경계 차이를 바꾸어 크기 또는 비율을 수정하세요.\"\n",
        "assert order_a_image.tobytes() != order_b_image.tobytes(), \"STEP 4에서 겹침 순서의 차이를 확인하세요.\"\n",
        "assert isinstance(image, Image.Image) and image.mode == \"RGB\" and image.size == (600, 400), \"600 × 400 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 all(color in visible_colors for color in current_palette), \"배경·사각형·타원·삼각형·윤곽색이 모두 보이도록 겹침을 조정하세요.\"\n",
        "\n",
        "assert Path(output_filename).is_file(), \"STEP 5 저장 셀을 실행하세요.\"\n",
        "with Image.open(output_filename) as checked_file:\n",
        "    assert checked_file.format == \"PNG\" and checked_file.mode == \"RGB\" and checked_file.size == image.size, \"PNG의 형식과 이미지 크기를 확인하세요.\"\n",
        "    assert checked_file.tobytes() == image.tobytes(), \"현재 이미지와 저장된 PNG가 다릅니다. STEP 5 저장 셀을 다시 실행하세요.\"\n",
        "mission_executions = (mission_step0_execution, mission_step1_execution, mission_step2_execution, mission_step3_execution, mission_step4_execution, mission_step5_execution, mission_final_execution)\n",
        "assert mission_executions == (1, 2, 3, 4, 5, 6, 7), \"직접 추가한 코드 셀이 있으면 지우고, 세션을 다시 시작한 뒤 모두 실행하세요.\"\n",
        "\n",
        "print(\"✅ 제출 정보와 RGB 범위 검사 통과\")\n",
        "print(\"✅ 기준점 적용·사각형 이동·타원 크기 변경 검사 통과\")\n",
        "print(\"✅ 겹침 비교·도형 가시성·최신 PNG 검사 통과\")\n",
        "print(\"✅ 새 런타임 전체 실행 검사 통과\")\n",
        "print(\"🎉 WEEK 03 IMAGE MISSION COMPLETE\")\n",
        "print(\"변경 기록 네 항목을 확인하고 .ipynb와 .png를 제출하세요.\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 선택 확장\n",
        "\n",
        "필수 미션과 변경 기록을 완료한 뒤 원하는 경우에만 진행합니다. 추가 점수는 없습니다.\n",
        "\n",
        "- 좌표와 순서를 고정하고 두 번째 팔레트를 적용해 다른 파일명으로 저장합니다.\n",
        "- 삼각형의 꼭짓점을 변경하고 형태의 차이를 비교합니다.\n",
        "\n",
        "필수 제출용 노트북은 그대로 두고 사본에서 실험하면 완료된 실행 결과와 파일을 보존할 수 있습니다."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "provenance": []
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
