{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# WEEK 05 MISSION: 통제된 우연 포스터 완성하기\n",
        "\n",
        "정해진 색상 후보와 크기 범위 안에서 위치·크기·색을 무작위로 선택하고, 크기 조건에 따라 원과 사각형이 달라지는 600 × 600 포스터를 만듭니다. 마지막 자동 검사에서 `WEEK 05 GENERATIVE MISSION COMPLETE`가 나오고 `.ipynb`와 `.png` 두 파일을 제출하면 남은 시간과 관계없이 바로 귀가할 수 있습니다.\n",
        "\n",
        "빈 화면에서 전체 코드를 외워 작성하지 않습니다. `EDIT` 표시가 있는 값과 네 줄만 수정하고, `DO NOT EDIT` 영역은 그대로 둡니다. 오류가 나오면 마지막 한글 안내를 읽고 해당 STEP부터 다시 실행합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# STEP 0: 준비 셀입니다. 이 셀은 수정하지 말고 실행만 하세요.\n",
        "from pathlib import Path\n",
        "import random\n",
        "from PIL import Image, ImageDraw\n",
        "from IPython.display import display\n",
        "\n",
        "canvas_width = 600\n",
        "canvas_height = 600\n",
        "\n",
        "STARTER_SEED_NUMBER = 11\n",
        "STARTER_SHAPE_COUNT = 36\n",
        "STARTER_MEDIUM_THRESHOLD = 40\n",
        "STARTER_LARGE_THRESHOLD = 60\n",
        "STARTER_BACKGROUND_COLOR = (245, 240, 228)\n",
        "STARTER_PRIMARY_COLOR = (37, 62, 78)\n",
        "STARTER_SECONDARY_COLOR = (111, 143, 126)\n",
        "STARTER_ACCENT_COLOR = (211, 155, 42)\n",
        "STARTER_OUTLINE_COLOR = (28, 31, 38)\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",
        "`=` 오른쪽의 세 문자열만 수정합니다. `design_rule`에는 무엇을 고정하고, 무엇을 무작위로 선택하며, 어떤 조건에서 무엇이 달라지는지 한 문장으로 작성합니다.\n",
        "\n",
        "예: `네 색과 여백은 고정하고 위치와 크기를 무작위로 선택하며, 크기가 64 이상이면 강조 사각형으로 그린다.`"
      ]
    },
    {
      "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",
        "design_rule = \"고정 요소, 무작위 요소, 조건 결과를 한 문장으로 작성하세요.\"\n",
        "\n",
        "# DO NOT EDIT: 아래 실행 확인 코드는 수정하지 마세요.\n",
        "mission_step1_execution = get_ipython().execution_count\n",
        "mission_stage = 1\n",
        "print(\"생성 규칙:\", design_rule)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## STEP 2: 생성 시스템의 값 설계\n",
        "\n",
        "아래 `EDIT` 영역에서 **시드, 도형 수, 두 조건 기준, 다섯 색상**을 수정합니다. 권장 시작값은 `seed_number = 73`, `shape_count = 56`, `medium_threshold = 42`, `large_threshold = 64`입니다.\n",
        "\n",
        "- 도형 수는 40~80으로 정합니다.\n",
        "- 조건은 `20 < medium_threshold < large_threshold < 80` 순서를 유지합니다.\n",
        "- 각 크기 구간이 너무 좁지 않도록 경계 사이에 10 이상의 차이를 둡니다.\n",
        "- 다섯 색상 역할은 서로 다르게 만들고 기준 팔레트에서 최소 세 역할을 변경합니다."
      ]
    },
    {
      "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",
        "seed_number = 11\n",
        "shape_count = 36\n",
        "medium_threshold = 40\n",
        "large_threshold = 60\n",
        "\n",
        "background_color = (245, 240, 228)\n",
        "primary_color = (37, 62, 78)\n",
        "secondary_color = (111, 143, 126)\n",
        "accent_color = (211, 155, 42)\n",
        "outline_color = (28, 31, 38)\n",
        "\n",
        "# DO NOT EDIT: 아래 고정 범위와 미리보기 코드는 수정하지 마세요.\n",
        "min_size = 20\n",
        "max_size = 80\n",
        "margin = 40\n",
        "palette = [primary_color, secondary_color, accent_color]\n",
        "output_filename = \"week05_\" + student_id + \"_\" + student_name + \"_seed\" + str(seed_number) + \".png\"\n",
        "\n",
        "assert type(seed_number) is int, \"seed_number를 따옴표 없는 정수로 작성하세요.\"\n",
        "assert type(shape_count) is int and 10 <= shape_count <= 100, \"shape_count를 10~100 정수로 작성하세요.\"\n",
        "assert type(medium_threshold) is int and type(large_threshold) is int, \"두 threshold를 따옴표 없는 정수로 작성하세요.\"\n",
        "assert min_size < medium_threshold < large_threshold < max_size, \"20 < medium_threshold < large_threshold < 80 순서를 확인하세요.\"\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",
        "\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",
        "starter_rules = (STARTER_SEED_NUMBER, STARTER_SHAPE_COUNT, STARTER_MEDIUM_THRESHOLD, STARTER_LARGE_THRESHOLD)\n",
        "current_rules = (seed_number, shape_count, medium_threshold, large_threshold)\n",
        "changed_rule_count = sum(current != starter for current, starter in zip(current_rules, starter_rules))\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_rule_count, \"개 / 완료 조건은 4개\")\n",
        "print(\"현재 변경한 색상 역할:\", changed_palette_count, \"개 / 완료 조건은 3개 이상\")\n",
        "print(\"저장 파일명:\", output_filename)\n",
        "mission_step2_execution = get_ipython().execution_count\n",
        "mission_stage = 2"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## STEP 3: 네 줄을 무작위 선택 코드로 바꾸고 이미지 생성\n",
        "\n",
        "처음에는 아래 셀을 수정하지 않고 실행합니다. 모든 반복이 같은 크기·위치·색을 사용하므로 도형이 한곳에 겹쳐 하나처럼 보입니다. 이것은 정답이 아니라 수정 전 출발점입니다.\n",
        "\n",
        "`EDIT A`의 네 줄을 다음과 같이 바꿉니다.\n",
        "\n",
        "```python\n",
        "size = random.randint(min_size, max_size)\n",
        "x = random.randint(margin, canvas_width - margin - size)\n",
        "y = random.randint(margin, canvas_height - margin - size)\n",
        "base_color = random.choice(palette)\n",
        "```\n",
        "\n",
        "그 아래의 `if / elif / else`는 수정하지 않습니다. STEP 2에서 정한 두 threshold가 세 분기의 범위를 바꿉니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# STEP 3 / EDIT A: 반복문 안의 네 시작 줄을 안내된 random 코드로 바꾸세요.\n",
        "assert mission_stage in (2, 3, 4), \"STEP 2 시스템 설계 셀을 먼저 실행하세요.\"\n",
        "image = Image.new(\"RGB\", (canvas_width, canvas_height), background_color)\n",
        "draw = ImageDraw.Draw(image)\n",
        "random.seed(seed_number)\n",
        "generation_records = []\n",
        "branch_counts = {\"large\": 0, \"medium\": 0, \"small\": 0}\n",
        "\n",
        "for shape_number in range(shape_count):\n",
        "    # EDIT A: 아래 네 줄을 markdown 안내의 random 코드로 바꾸세요.\n",
        "    size = min_size\n",
        "    x = margin\n",
        "    y = margin\n",
        "    base_color = palette[0]\n",
        "\n",
        "    # DO NOT EDIT: 아래 조건 분기와 기록 코드는 수정하지 마세요.\n",
        "    if size >= large_threshold:\n",
        "        branch_name = \"large\"\n",
        "        draw.rectangle(\n",
        "            (x, y, x + size, y + size),\n",
        "            fill=accent_color,\n",
        "            outline=outline_color,\n",
        "            width=3,\n",
        "        )\n",
        "    elif size >= medium_threshold:\n",
        "        branch_name = \"medium\"\n",
        "        draw.ellipse(\n",
        "            (x, y, x + size, y + size),\n",
        "            fill=base_color,\n",
        "        )\n",
        "    else:\n",
        "        branch_name = \"small\"\n",
        "        draw.ellipse(\n",
        "            (x, y, x + size, y + size),\n",
        "            fill=background_color,\n",
        "            outline=base_color,\n",
        "            width=4,\n",
        "        )\n",
        "\n",
        "    branch_counts[branch_name] += 1\n",
        "    generation_records.append((shape_number, size, x, y, base_color, branch_name))\n",
        "\n",
        "display(image)\n",
        "print(\"서로 다른 크기 수:\", len({record[1] for record in generation_records}))\n",
        "print(\"서로 다른 위치 수:\", len({(record[2], record[3]) for record in generation_records}))\n",
        "print(\"선택된 팔레트 색상 수:\", len({record[4] for record in generation_records}))\n",
        "print(\"조건 분기 횟수:\", branch_counts)\n",
        "mission_step3_execution = get_ipython().execution_count\n",
        "mission_stage = 3"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## STEP 4: 최종 PNG 저장\n",
        "\n",
        "이미지와 생성 규칙 문장이 연결되는지 확인한 뒤 아래 셀을 실행합니다. 저장 후 표시되는 이미지와 파일명을 확인합니다. 시드, 색상, 조건 기준 또는 STEP 3 코드를 다시 바꾸었다면 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 05 GENERATIVE MISSION COMPLETE`가 나타나면 `.ipynb`와 `.png`를 제출하고 바로 귀가할 수 있습니다.\n",
        "\n",
        "자동 검사는 미적 취향을 채점하지 않습니다. 제출 정보, 규칙값 변경, RGB 범위, 무작위 선택의 작동, 조건 분기, 안전한 좌표, 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 all(character not in student_id + student_name for character in (\"/\", \"\\\\\")), \"학번과 이름에는 / 또는 \\\\ 문자를 사용할 수 없습니다.\"\n",
        "assert type(design_rule) is str and len(design_rule.strip()) >= 20 and design_rule != \"고정 요소, 무작위 요소, 조건 결과를 한 문장으로 작성하세요.\", \"design_rule에 생성 규칙을 20자 이상 작성하세요.\"\n",
        "expected_output_filename = \"week05_\" + student_id + \"_\" + student_name + \"_seed\" + str(seed_number) + \".png\"\n",
        "assert output_filename == expected_output_filename, \"STEP 2를 다시 실행해 PNG 파일명을 현재 시드와 맞추세요.\"\n",
        "\n",
        "assert type(seed_number) is int and 0 <= seed_number <= 9999 and seed_number != STARTER_SEED_NUMBER, \"seed_number를 기준값 11과 다른 0~9999 정수로 바꾸세요.\"\n",
        "assert type(shape_count) is int and 40 <= shape_count <= 80 and shape_count != STARTER_SHAPE_COUNT, \"shape_count를 기준값과 다른 40~80 정수로 바꾸세요.\"\n",
        "assert (min_size, max_size, margin) == (20, 80, 40), \"min_size, max_size, margin은 제공된 20, 80, 40을 유지하세요.\"\n",
        "assert type(medium_threshold) is int and type(large_threshold) is int, \"두 threshold를 정수로 작성하세요.\"\n",
        "assert min_size < medium_threshold < large_threshold < max_size, \"20 < medium_threshold < large_threshold < 80 순서를 확인하세요.\"\n",
        "assert medium_threshold - min_size >= 10 and large_threshold - medium_threshold >= 10 and max_size - large_threshold >= 10, \"작은·중간·큰 크기 구간을 각각 10 이상 확보하세요.\"\n",
        "assert medium_threshold != STARTER_MEDIUM_THRESHOLD and large_threshold != STARTER_LARGE_THRESHOLD, \"medium_threshold와 large_threshold를 기준값 40, 60과 모두 다르게 바꾸세요.\"\n",
        "\n",
        "current_palette = (background_color, primary_color, secondary_color, accent_color, outline_color)\n",
        "starter_palette = (STARTER_BACKGROUND_COLOR, STARTER_PRIMARY_COLOR, STARTER_SECONDARY_COLOR, STARTER_ACCENT_COLOR, STARTER_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",
        "changed_palette_count = sum(current != starter for current, starter in zip(current_palette, starter_palette))\n",
        "assert changed_palette_count >= 3, \"기준 팔레트에서 색상 역할을 최소 세 개 변경하세요.\"\n",
        "assert palette == [primary_color, secondary_color, accent_color], \"palette 구성 코드는 수정하지 마세요.\"\n",
        "\n",
        "assert type(generation_records) is list and len(generation_records) == shape_count, \"STEP 3에서 shape_count만큼 도형을 생성하세요.\"\n",
        "checker_random = random.Random(seed_number)\n",
        "expected_records = []\n",
        "for shape_number in range(shape_count):\n",
        "    expected_size = checker_random.randint(min_size, max_size)\n",
        "    expected_x = checker_random.randint(margin, canvas_width - margin - expected_size)\n",
        "    expected_y = checker_random.randint(margin, canvas_height - margin - expected_size)\n",
        "    expected_color = checker_random.choice(palette)\n",
        "    if expected_size >= large_threshold:\n",
        "        expected_branch = \"large\"\n",
        "    elif expected_size >= medium_threshold:\n",
        "        expected_branch = \"medium\"\n",
        "    else:\n",
        "        expected_branch = \"small\"\n",
        "    expected_records.append((shape_number, expected_size, expected_x, expected_y, expected_color, expected_branch))\n",
        "assert generation_records == expected_records, \"STEP 3의 네 시작 줄을 안내된 randint·choice 코드로 정확히 바꾸세요.\"\n",
        "assert len({record[1] for record in generation_records}) >= 8, \"크기가 충분히 달라지지 않습니다. STEP 3의 size 줄을 확인하세요.\"\n",
        "assert len({(record[2], record[3]) for record in generation_records}) >= shape_count // 2, \"위치가 충분히 달라지지 않습니다. STEP 3의 x와 y 줄을 확인하세요.\"\n",
        "assert len({record[4] for record in generation_records}) >= 2, \"팔레트 색상이 선택되지 않습니다. STEP 3의 base_color 줄을 확인하세요.\"\n",
        "assert len({record[5] for record in generation_records}) >= 2, \"조건 분기가 두 종류 이상 나타나도록 시드와 threshold를 확인하세요.\"\n",
        "assert all(margin <= x and margin <= y and x + size <= canvas_width - margin and y + size <= canvas_height - margin for _, size, x, y, _, _ in generation_records), \"모든 도형이 40픽셀 안전 여백 안에 있도록 x와 y 범위를 확인하세요.\"\n",
        "expected_branch_counts = {name: sum(record[5] == name for record in generation_records) for name in (\"large\", \"medium\", \"small\")}\n",
        "assert branch_counts == expected_branch_counts, \"STEP 3의 조건 분기와 기록 코드는 수정하지 마세요.\"\n",
        "\n",
        "assert isinstance(image, Image.Image) and image.mode == \"RGB\" and image.size == (600, 600), \"600 × 600 RGB 이미지를 다시 생성하세요.\"\n",
        "assert image.getpixel((0, 0)) == background_color, \"안전 여백과 배경색을 유지하세요.\"\n",
        "visible_color_counts = image.getcolors(maxcolors=canvas_width * canvas_height)\n",
        "assert visible_color_counts is not None and len(visible_color_counts) >= 4, \"배경과 여러 도형 색상이 보이도록 STEP 2와 STEP 3을 확인하세요.\"\n",
        "non_background_pixels = sum(count for count, color in visible_color_counts if color != background_color)\n",
        "assert non_background_pixels >= 1000, \"도형이 충분히 보이지 않습니다. STEP 3의 무작위 선택 코드를 확인하세요.\"\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(\"✅ randint·choice와 안전한 좌표 검사 통과\")\n",
        "print(\"✅ if / elif / else 분기 결과 검사 통과\")\n",
        "print(\"✅ 600 × 600 RGB 이미지와 PNG 저장 검사 통과\")\n",
        "print(\"✅ 새 런타임 전체 실행 검사 통과\")\n",
        "print(\"🎉 WEEK 05 GENERATIVE MISSION COMPLETE\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 선택 확장\n",
        "\n",
        "필수 미션을 완료한 뒤 더 실험하고 싶은 경우에만 진행합니다. 추가 점수는 없습니다.\n",
        "\n",
        "- 시드만 바꾸어 두 번째 결과를 만들고 다른 파일명으로 저장합니다.\n",
        "- 같은 시드에서 두 threshold만 바꾸어 큰·중간·작은 도형의 비율을 비교합니다.\n",
        "- 필수 제출을 먼저 완료한 뒤 조건 분기의 색상 또는 도형을 한 가지 바꾸어 봅니다."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "provenance": []
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
