mobile wallpaper 1
mobile wallpaper 2
mobile wallpaper 3
mobile wallpaper 4
mobile wallpaper 5
mobile wallpaper 6
166 자
1 분
Java 영화관 예매 프로그램 완성본 소스코드
2019-05-17

예전에 작업하던 영화 예매 프로그램 중간과정의 마지막 단계인 예매 파트 완성.

코드 난잡하게 널려있던 부분을 모두 정리했다.

  1. 상영관 관리 프로그램 O
  2. 영화 상형일정 관리 프로그램 O
  3. 영화 티켓 예매 프로그램 O
  4. 균일한 출력 및 사용법 O

예외처리 할 것이 아직 남은 것 같긴 한데 최대한 처리를 했으니 사용하는데 큰 무리는 없을 것이라 생각된다.

아래는 각 클래스의 소스코드.

InputClosedException#

/**
* 입력 스트림이 닫혔을 때(Ctrl+D, Ctrl+Z 등) 프로그램을 안전하게 종료하기 위한 예외.
*/
public class InputClosedException extends RuntimeException
{
private static final long serialVersionUID = 1L;
public InputClosedException()
{
super("입력 스트림이 종료되었습니다.");
}
}

ConsoleUI#

import java.util.List;
/**
* 콘솔 출력 전용 유틸리티. (제목, 안내/오류 메시지, 표 출력)
* <p>
* 한글은 콘솔에서 영문보다 2배 넓게 표시되므로, 표를 정렬할 때 글자 수가 아닌 "표시 폭"을 기준으로 맞춘다.
*/
public class ConsoleUI
{
public static final int WIDTH = 64;
private ConsoleUI()
{
}
public static void title(String text)
{
System.out.println();
System.out.println(repeat('=', WIDTH));
System.out.println(center(text, WIDTH));
System.out.println(repeat('=', WIDTH));
}
public static void section(String text)
{
System.out.println();
String head = "---- " + text + " ";
System.out.println(head + repeat('-', Math.max(4, WIDTH - displayWidth(head))));
}
public static void success(String message)
{
System.out.println(" [완료] " + message);
}
public static void error(String message)
{
System.out.println(" [오류] " + message);
}
public static void info(String message)
{
System.out.println(" [안내] " + message);
}
/** 헤더와 행 데이터를 받아 테두리가 있는 표로 출력한다. */
public static void printTable(String[] headers, List<String[]> rows)
{
int[] widths = new int[headers.length];
for (int i = 0; i < headers.length; i++)
{
widths[i] = displayWidth(headers[i]);
}
for (String[] row : rows)
{
for (int i = 0; i < headers.length; i++)
{
widths[i] = Math.max(widths[i], displayWidth(row[i]));
}
}
String border = buildBorder(widths);
System.out.println(border);
System.out.println(buildRow(headers, widths));
System.out.println(border);
for (String[] row : rows)
{
System.out.println(buildRow(row, widths));
}
System.out.println(border);
}
private static String buildBorder(int[] widths)
{
StringBuilder sb = new StringBuilder(" +");
for (int w : widths)
{
sb.append(repeat('-', w + 2)).append('+');
}
return sb.toString();
}
private static String buildRow(String[] cells, int[] widths)
{
StringBuilder sb = new StringBuilder(" |");
for (int i = 0; i < widths.length; i++)
{
sb.append(' ').append(pad(cells[i], widths[i])).append(" |");
}
return sb.toString();
}
/** 문자열의 콘솔 표시 폭 (한글/전각 문자는 2칸) */
public static int displayWidth(String s)
{
int width = 0;
for (int i = 0; i < s.length(); i++)
{
width += isWide(s.charAt(i)) ? 2 : 1;
}
return width;
}
private static boolean isWide(char c)
{
return (c >= 0x1100 && c <= 0x115F) // 한글 자모
|| (c >= 0x2E80 && c <= 0xA4CF) // CJK
|| (c >= 0xAC00 && c <= 0xD7A3) // 한글 음절
|| (c >= 0xF900 && c <= 0xFAFF)
|| (c >= 0xFE30 && c <= 0xFE4F)
|| (c >= 0xFF00 && c <= 0xFF60) // 전각 문자
|| (c >= 0xFFE0 && c <= 0xFFE6);
}
/** 표시 폭 기준으로 오른쪽을 공백으로 채운다. */
public static String pad(String s, int width)
{
StringBuilder sb = new StringBuilder(s);
for (int w = displayWidth(s); w < width; w++)
{
sb.append(' ');
}
return sb.toString();
}
public static String center(String s, int width)
{
int left = Math.max(0, (width - displayWidth(s)) / 2);
return repeat(' ', left) + s;
}
public static String repeat(char c, int n)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++)
{
sb.append(c);
}
return sb.toString();
}
}

ConsoleInput#

import java.util.NoSuchElementException;
import java.util.Scanner;
/**
* 콘솔 입력 전용 유틸리티.
* <p>
* - 프로그램 전체가 하나의 Scanner 를 공유한다. (System.in 에 Scanner 를 여러 개 만들면 입력이 유실될 수 있음)
* - 모든 입력을 한 줄 단위(nextLine)로 읽어서, nextInt() 뒤에 남는 개행 문제를 없앤다.
* - 숫자 입력에 문자가 들어오거나 범위를 벗어나면 예외로 죽지 않고 다시 입력받는다.
*/
public class ConsoleInput
{
private static final Scanner SCANNER = new Scanner(System.in);
private ConsoleInput()
{
}
/** 한 줄을 읽어 앞뒤 공백을 제거해 돌려준다. */
public static String readLine(String prompt)
{
System.out.print(" " + prompt);
try
{
return SCANNER.nextLine().trim();
} catch (NoSuchElementException | IllegalStateException e)
{
throw new InputClosedException();
}
}
/** min ~ max 범위의 정수를 입력받는다. 올바른 값이 들어올 때까지 반복한다. */
public static int readInt(String prompt, int min, int max)
{
while (true)
{
String line = readLine(prompt);
if (line.isEmpty())
{
ConsoleUI.error("값이 입력되지 않았습니다. " + min + " ~ " + max + " 사이의 숫자를 입력해주세요.");
continue;
}
try
{
int value = Integer.parseInt(line);
if (value < min || value > max)
{
ConsoleUI.error(value + "은(는) 범위를 벗어났습니다. " + min + " ~ " + max + " 사이의 숫자를 입력해주세요.");
continue;
}
return value;
} catch (NumberFormatException e)
{
ConsoleUI.error("'" + line + "'은(는) 숫자가 아닙니다. " + min + " ~ " + max + " 사이의 숫자를 입력해주세요.");
}
}
}
/** y/n 질문. 한글 자판 상태에서 누른 ㅛ/ㅜ 도 허용한다. */
public static boolean confirm(String prompt)
{
while (true)
{
String line = readLine(prompt + " (y/n) : ").toLowerCase();
if (line.equals("y") || line.equals("yes") || line.equals("ㅛ"))
{
return true;
}
if (line.equals("n") || line.equals("no") || line.equals("ㅜ"))
{
return false;
}
ConsoleUI.error("y(예) 또는 n(아니오)로 입력해주세요.");
}
}
}

Screen#

public class Screen
{
private String name;
private int rowNum;
private int seatNum;
public Screen(String name, int rowNum, int seatNum)
{
this.name = name;
this.rowNum = rowNum;
this.seatNum = seatNum;
}
/** 총 좌석 수 */
public int getTotalSeats()
{
return rowNum * seatNum;
}
/** 예) 5열 x 10석 */
public String getLayoutText()
{
return rowNum + "열 x " + seatNum + "석";
}
@Override
public String toString()
{
return "Screen [name=" + name + ", rowNum=" + rowNum + ", seatNum=" + seatNum + "]";
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getRowNum()
{
return rowNum;
}
public void setRowNum(int rowNum)
{
this.rowNum = rowNum;
}
public int getSeatNum()
{
return seatNum;
}
public void setSeatNum(int seatNum)
{
this.seatNum = seatNum;
}
}

ScreenManager#

import java.util.ArrayList;
import java.util.List;
public class ScreenManager
{
public static final int MAX_ROWS = 20;
public static final int MAX_SEATS_PER_ROW = 20;
private static final int MAX_NAME_WIDTH = 20;
private Screen[] screens;
private int maxCount;
private int count;
private TimetableManager tm; // 상영일정이 걸린 상영관은 삭제하지 못하도록 확인용
public ScreenManager(int maxCount)
{
this.maxCount = maxCount;
this.screens = new Screen[maxCount];
count = 0;
}
public void setTimetableManager(TimetableManager tm)
{
this.tm = tm;
}
public void add()
{
ConsoleUI.section("상영관 등록");
if (count == maxCount)
{
ConsoleUI.error("상영관을 더 이상 등록할 수 없습니다. (최대 " + maxCount + "개)");
ConsoleUI.info("사용하지 않는 상영관을 삭제한 뒤 다시 시도해주세요.");
return;
}
String name;
while (true)
{
name = ConsoleInput.readLine("상영관 이름 (예: 1관, 빈 입력 시 취소) : ");
if (name.isEmpty())
{
ConsoleUI.info("상영관 등록을 취소했습니다.");
return;
}
if (ConsoleUI.displayWidth(name) > MAX_NAME_WIDTH)
{
ConsoleUI.error("이름이 너무 깁니다. (한글 10자 / 영문 20자 이내)");
continue;
}
if (findByName(name) != null)
{
ConsoleUI.error("'" + name + "'은(는) 이미 등록된 상영관입니다. 다른 이름을 입력해주세요.");
continue;
}
break;
}
int rowNum = ConsoleInput.readInt("좌석 열 수 (1~" + MAX_ROWS + ") : ", 1, MAX_ROWS);
int seatNum = ConsoleInput.readInt("한 열의 좌석 수 (1~" + MAX_SEATS_PER_ROW + ") : ", 1, MAX_SEATS_PER_ROW);
Screen screen = new Screen(name, rowNum, seatNum);
addScreen(screen);
ConsoleUI.success("'" + name + "' 상영관이 등록되었습니다. (" + screen.getLayoutText() + ", 총 "
+ screen.getTotalSeats() + "석) [" + count + "/" + maxCount + "]");
}
/** 입력 없이 상영관을 추가한다. (샘플 데이터용) */
public boolean addScreen(Screen screen)
{
if (count == maxCount || findByName(screen.getName()) != null)
{
return false;
}
screens[count] = screen;
count++;
return true;
}
public void list()
{
ConsoleUI.section("상영관 목록 (" + count + "/" + maxCount + ")");
if (count == 0)
{
ConsoleUI.info("등록된 상영관이 없습니다. 1번(등록)으로 상영관을 추가해보세요.");
return;
}
printScreenTable();
}
private void printScreenTable()
{
List<String[]> rows = new ArrayList<String[]>();
for (int i = 0; i < count; i++)
{
MovieTimetable mt = (tm == null) ? null : tm.findByScreen(screens[i]);
rows.add(new String[] {
String.valueOf(i + 1),
screens[i].getName(),
screens[i].getLayoutText(),
screens[i].getTotalSeats() + "석",
mt == null ? "-" : mt.getMovie().getTitle()
});
}
ConsoleUI.printTable(new String[] {"번호", "상영관", "좌석 배치", "총 좌석", "상영 중인 영화"}, rows);
}
public void remove()
{
ConsoleUI.section("상영관 삭제");
if (count == 0)
{
ConsoleUI.info("삭제할 상영관이 없습니다.");
return;
}
printScreenTable();
int select = ConsoleInput.readInt("삭제할 상영관 번호 (0. 취소) >> ", 0, count);
if (select == 0)
{
ConsoleUI.info("삭제를 취소했습니다.");
return;
}
int index = select - 1;
Screen target = screens[index];
if (tm != null && tm.findByScreen(target) != null)
{
ConsoleUI.error("'" + target.getName() + "'에는 상영일정이 등록되어 있어 삭제할 수 없습니다.");
ConsoleUI.info("상영일정 관리에서 해당 일정을 먼저 삭제해주세요.");
return;
}
if (!ConsoleInput.confirm("'" + target.getName() + "' 상영관을 삭제할까요?"))
{
ConsoleUI.info("삭제를 취소했습니다.");
return;
}
// 뒤의 원소를 한 칸씩 당긴다. (j + 1 이 배열 범위를 넘지 않도록 count - 1 까지만)
for (int j = index; j < count - 1; j++)
{
screens[j] = screens[j + 1];
}
screens[count - 1] = null;
count--;
ConsoleUI.success("'" + target.getName() + "' 상영관이 삭제되었습니다.");
}
public void screenManagerMain()
{
while (true)
{
ConsoleUI.title("상영관 관리 (등록 " + count + "/" + maxCount + ")");
System.out.println(" 1. 상영관 등록");
System.out.println(" 2. 상영관 목록");
System.out.println(" 3. 상영관 삭제");
System.out.println(" 0. 메인 메뉴로");
int input = ConsoleInput.readInt("메뉴 선택 >> ", 0, 3);
switch (input)
{
case 1:
add();
break;
case 2:
list();
break;
case 3:
remove();
break;
case 0:
return;
default:
break;
}
}
}
public Screen findByName(String name)
{
for (int i = 0; i < count; i++)
{
if (screens[i].getName().equals(name))
{
return screens[i];
}
}
return null;
}
public Screen getScreen(int index)
{
if (index < 0 || index >= count)
{
throw new IndexOutOfBoundsException("상영관 번호가 범위를 벗어났습니다: " + index);
}
return screens[index];
}
public Screen[] getScreens()
{
if (count == 0)
{
return null;
}
return screens;
}
public int getMaxCount()
{
return maxCount;
}
public int getCount()
{
return count;
}
}

Movie#

public class Movie
{
private String title;
private String openDate;
private int playtime;
private String grade;
public Movie(String title, String openDate, int playtime, String grade)
{
this.title = title;
this.openDate = openDate;
this.playtime = playtime;
this.grade = grade;
}
/** 예) 2014-11-06 개봉 / 169분 / 12세이상관람가 */
public String getSummary()
{
return openDate + " 개봉 / " + playtime + "분 / " + grade;
}
public String getTitle()
{
return title;
}
public void setTitle(String title)
{
this.title = title;
}
public String getOpenDate()
{
return openDate;
}
public void setOpenDate(String openDate)
{
this.openDate = openDate;
}
public int getPlaytime()
{
return playtime;
}
public void setPlaytime(int playtime)
{
this.playtime = playtime;
}
public String getGrade()
{
return grade;
}
public void setGrade(String grade)
{
this.grade = grade;
}
}

MovieTimetable#

import java.util.Arrays;
/**
* 한 상영관에서 한 영화를 상영하는 일정. (상영 시작시간 여러 개)
*/
public class MovieTimetable
{
private static final int MINUTES_OF_DAY = 24 * 60;
private Movie movie;
private Screen screen;
private String[] times;
public MovieTimetable(Movie movie, Screen screen, String[] times)
{
this.movie = movie;
this.screen = screen;
this.times = times;
}
/**
* "10:00 13:30 17:00" 형태의 문자열을 검사한 뒤 정렬된 시간 배열로 변환한다.
* 형식이 틀리거나, 중복되거나, 러닝타임 때문에 회차가 겹치면 IllegalArgumentException 을 던진다.
*/
public static String[] parseTimes(String input, int playtime)
{
String trimmed = input.trim();
if (trimmed.isEmpty())
{
throw new IllegalArgumentException("상영 시작시간을 하나 이상 입력해주세요.");
}
String[] tokens = trimmed.split("[\\s,]+");
int[] minutes = new int[tokens.length];
for (int i = 0; i < tokens.length; i++)
{
minutes[i] = toMinutes(tokens[i]);
}
Arrays.sort(minutes);
for (int i = 1; i < minutes.length; i++)
{
if (minutes[i] == minutes[i - 1])
{
throw new IllegalArgumentException(formatMinutes(minutes[i]) + " 이(가) 중복 입력되었습니다.");
}
if (minutes[i] - minutes[i - 1] < playtime)
{
throw new IllegalArgumentException(formatMinutes(minutes[i - 1]) + " 회차(종료 "
+ formatMinutes(minutes[i - 1] + playtime) + ")와 " + formatMinutes(minutes[i])
+ " 회차가 겹칩니다. (러닝타임 " + playtime + "분)");
}
}
String[] result = new String[minutes.length];
for (int i = 0; i < minutes.length; i++)
{
result[i] = formatMinutes(minutes[i]);
}
return result;
}
private static int toMinutes(String time)
{
if (!time.matches("\\d{1,2}:\\d{2}"))
{
throw new IllegalArgumentException("'" + time + "'은(는) 올바른 시간 형식이 아닙니다. 예) 09:30 13:00");
}
String[] parts = time.split(":");
int hour = Integer.parseInt(parts[0]);
int minute = Integer.parseInt(parts[1]);
if (hour > 23 || minute > 59)
{
throw new IllegalArgumentException("'" + time + "'은(는) 존재하지 않는 시간입니다. (00:00 ~ 23:59)");
}
return hour * 60 + minute;
}
/** 분 단위 값을 HH:mm 으로. 다음날로 넘어가면 (+1) 표시 */
private static String formatMinutes(int total)
{
int m = total % MINUTES_OF_DAY;
String text = String.format("%02d:%02d", m / 60, m % 60);
if (total >= MINUTES_OF_DAY)
{
text += "(+1)";
}
return text;
}
/** 시작시간으로부터 러닝타임을 더한 종료시간 */
public String getEndTime(String startTime)
{
return formatMinutes(toMinutes(startTime) + movie.getPlaytime());
}
public boolean hasTime(String time)
{
for (String t : times)
{
if (t.equals(time))
{
return true;
}
}
return false;
}
/** 예) 10:00, 13:30, 17:00 */
public String getTimesText()
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < times.length; i++)
{
if (i > 0)
{
sb.append(", ");
}
sb.append(times[i]);
}
return sb.toString();
}
public void showTime()
{
System.out.print(getTimesText());
}
public Movie getMovie()
{
return movie;
}
public void setMovie(Movie movie)
{
this.movie = movie;
}
public Screen getScreen()
{
return screen;
}
public void setScreen(Screen screen)
{
this.screen = screen;
}
public String[] getTimes()
{
return times;
}
public void setTimes(String[] times)
{
this.times = times;
}
}

TimetableManager#

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;
public class TimetableManager
{
private static final String[] GRADES = {"전체관람가", "12세이상관람가", "15세이상관람가", "청소년관람불가"};
private static final int MAX_TITLE_WIDTH = 30;
private static final int MAX_PLAYTIME = 600;
private MovieTimetable[] table;
private ScreenManager sm;
private TicketManager ticketManager; // 잔여석 표시, 예매가 있는 일정 삭제 방지용
private int pos;
public TimetableManager(int n, ScreenManager sm)
{
table = new MovieTimetable[n];
this.sm = sm;
pos = 0;
}
public void setTicketManager(TicketManager ticketManager)
{
this.ticketManager = ticketManager;
}
// ------------------------------------------------------------------ 등록
public void add()
{
ConsoleUI.section("상영일정 등록");
if (sm.getCount() == 0)
{
ConsoleUI.error("등록된 상영관이 없습니다.");
ConsoleUI.info("메인 메뉴 1번(상영관 관리)에서 상영관을 먼저 등록해주세요.");
return;
}
if (isFull())
{
ConsoleUI.error("상영일정이 가득 찼습니다. (최대 " + table.length + "개)");
ConsoleUI.info("기존 상영일정을 삭제한 뒤 다시 시도해주세요.");
return;
}
if (getAvailableScreens().isEmpty())
{
ConsoleUI.error("모든 상영관에 이미 상영일정이 있습니다. (상영관 1곳당 영화 1편)");
ConsoleUI.info("상영관을 새로 등록하거나 기존 상영일정을 삭제해주세요.");
return;
}
Movie movie = selectOrCreateMovie();
if (movie == null)
{
ConsoleUI.info("상영일정 등록을 취소했습니다.");
return;
}
int added = 0;
while (true)
{
if (isFull())
{
ConsoleUI.info("상영일정이 가득 차서 더 이상 등록할 수 없습니다.");
break;
}
List<Screen> available = getAvailableScreens();
if (available.isEmpty())
{
ConsoleUI.info("일정을 추가할 수 있는 빈 상영관이 더 없습니다.");
break;
}
System.out.println();
System.out.println(" '" + movie.getTitle() + "'을(를) 상영할 상영관을 선택하세요. (비어 있는 상영관만 표시)");
List<String[]> rows = new ArrayList<String[]>();
for (int i = 0; i < available.size(); i++)
{
Screen sc = available.get(i);
rows.add(new String[] {String.valueOf(i + 1), sc.getName(), sc.getLayoutText(), sc.getTotalSeats() + "석"});
}
ConsoleUI.printTable(new String[] {"번호", "상영관", "좌석 배치", "총 좌석"}, rows);
String zeroLabel = (added == 0) ? "0. 취소" : "0. 등록 마치기";
int select = ConsoleInput.readInt("상영관 번호 (" + zeroLabel + ") >> ", 0, available.size());
if (select == 0)
{
break;
}
Screen screen = available.get(select - 1);
String[] times = readTimes(movie);
if (times == null)
{
ConsoleUI.info("'" + screen.getName() + "' 일정 입력을 취소했습니다.");
continue;
}
MovieTimetable mt = new MovieTimetable(movie, screen, times);
addTimetable(mt);
added++;
ConsoleUI.success(screen.getName() + "에 '" + movie.getTitle() + "' " + times.length + "회차가 등록되었습니다.");
for (String t : times)
{
System.out.println(" " + t + " ~ " + mt.getEndTime(t));
}
}
if (added == 0)
{
ConsoleUI.info("등록된 상영일정이 없습니다.");
} else
{
ConsoleUI.success("총 " + added + "개 상영관에 '" + movie.getTitle() + "' 일정이 등록되었습니다. [" + pos + "/"
+ table.length + "]");
}
}
/** 기존 영화 중 선택하거나 새 영화를 입력받는다. 취소하면 null */
private Movie selectOrCreateMovie()
{
List<Movie> movies = getMovies();
if (movies.isEmpty())
{
ConsoleUI.info("등록된 영화가 없어 새 영화 정보를 입력합니다.");
return createMovie();
}
System.out.println(" 등록된 영화 목록");
List<String[]> rows = new ArrayList<String[]>();
for (int i = 0; i < movies.size(); i++)
{
Movie m = movies.get(i);
rows.add(new String[] {String.valueOf(i + 1), m.getTitle(), m.getOpenDate(), m.getPlaytime() + "분", m.getGrade()});
}
ConsoleUI.printTable(new String[] {"번호", "제목", "개봉일", "러닝타임", "등급"}, rows);
int select = ConsoleInput.readInt("영화 번호 (0. 새 영화 등록) >> ", 0, movies.size());
if (select == 0)
{
return createMovie();
}
return movies.get(select - 1);
}
private Movie createMovie()
{
String title;
while (true)
{
title = ConsoleInput.readLine("영화 제목 (빈 입력 시 취소) : ");
if (title.isEmpty())
{
return null;
}
if (ConsoleUI.displayWidth(title) > MAX_TITLE_WIDTH)
{
ConsoleUI.error("제목이 너무 깁니다. (한글 15자 / 영문 30자 이내)");
continue;
}
break;
}
Movie existing = findMovie(title);
if (existing != null)
{
ConsoleUI.info("'" + title + "'은(는) 이미 등록된 영화입니다. 기존 정보(" + existing.getSummary() + ")를 사용합니다.");
return existing;
}
String openDate = readDate("개봉일 (예: 2026-09-24 또는 20260924) : ");
int playtime = ConsoleInput.readInt("러닝타임 (분, 1~" + MAX_PLAYTIME + ") : ", 1, MAX_PLAYTIME);
System.out.print(" 등급 -");
for (int i = 0; i < GRADES.length; i++)
{
System.out.print(" " + (i + 1) + "." + GRADES[i]);
}
System.out.println();
int gradeNo = ConsoleInput.readInt("등급 번호 (1~" + GRADES.length + ") : ", 1, GRADES.length);
Movie movie = new Movie(title, openDate, playtime, GRADES[gradeNo - 1]);
ConsoleUI.success("새 영화 '" + title + "' 정보가 입력되었습니다. (" + movie.getSummary() + ")");
return movie;
}
private String readDate(String prompt)
{
while (true)
{
String input = ConsoleInput.readLine(prompt);
try
{
LocalDate date = input.contains("-")
? LocalDate.parse(input, DateTimeFormatter.ISO_LOCAL_DATE)
: LocalDate.parse(input, DateTimeFormatter.BASIC_ISO_DATE);
return date.toString();
} catch (DateTimeParseException e)
{
ConsoleUI.error("'" + input + "'은(는) 올바른 날짜가 아닙니다. 예) 2026-09-24");
}
}
}
/** 상영 시작시간을 입력받는다. 빈 입력이면 null */
private String[] readTimes(Movie movie)
{
System.out.println(" 상영 시작시간을 공백으로 구분해 입력하세요. (러닝타임 " + movie.getPlaytime() + "분, 회차끼리 겹칠 수 없음)");
while (true)
{
String input = ConsoleInput.readLine("시작시간 (예: 10:00 13:30, 빈 입력 시 취소) : ");
if (input.isEmpty())
{
return null;
}
try
{
return MovieTimetable.parseTimes(input, movie.getPlaytime());
} catch (IllegalArgumentException e)
{
ConsoleUI.error(e.getMessage());
}
}
}
/** 입력 없이 상영일정을 추가한다. (샘플 데이터 및 add() 내부에서 사용) */
public boolean addTimetable(MovieTimetable mt)
{
if (isFull() || findByScreen(mt.getScreen()) != null)
{
return false;
}
table[pos] = mt;
pos++;
sort();
return true;
}
/** 영화 제목 → 상영관 이름 순으로 정렬 (버블 정렬) */
private void sort()
{
for (int i = 0; i < pos - 1; i++)
{
for (int j = 0; j < pos - 1 - i; j++)
{
int cmp = table[j].getMovie().getTitle().compareTo(table[j + 1].getMovie().getTitle());
if (cmp == 0)
{
cmp = table[j].getScreen().getName().compareTo(table[j + 1].getScreen().getName());
}
if (cmp > 0)
{
MovieTimetable temp = table[j];
table[j] = table[j + 1];
table[j + 1] = temp;
}
}
}
}
// ------------------------------------------------------------------ 목록
public void list()
{
ConsoleUI.section("상영일정 목록 (" + pos + "/" + table.length + ")");
if (pos == 0)
{
ConsoleUI.info("등록된 상영일정이 없습니다. 1번(등록)으로 추가해보세요.");
return;
}
List<Movie> movies = getMovies();
for (int i = 0; i < movies.size(); i++)
{
Movie m = movies.get(i);
System.out.println();
System.out.println(" [" + (i + 1) + "] " + m.getTitle() + " " + m.getSummary());
for (int k = 0; k < pos; k++)
{
if (table[k].getMovie() != m)
{
continue;
}
Screen sc = table[k].getScreen();
System.out.println(" - " + sc.getName() + " (" + sc.getLayoutText() + ")");
for (String t : table[k].getTimes())
{
String line = " " + t + " ~ " + table[k].getEndTime(t);
if (ticketManager != null)
{
int remain = sc.getTotalSeats() - ticketManager.countReserved(sc, t);
line += " " + (remain == 0 ? "매진" : "잔여 " + remain + "/" + sc.getTotalSeats() + "석");
}
System.out.println(line);
}
}
}
}
// ------------------------------------------------------------------ 삭제
public void remove()
{
ConsoleUI.section("상영일정 삭제");
if (pos == 0)
{
ConsoleUI.info("삭제할 상영일정이 없습니다.");
return;
}
List<String[]> rows = new ArrayList<String[]>();
for (int i = 0; i < pos; i++)
{
int ticketCount = (ticketManager == null) ? 0 : ticketManager.countTickets(table[i].getScreen());
rows.add(new String[] {
String.valueOf(i + 1),
table[i].getMovie().getTitle(),
table[i].getScreen().getName(),
table[i].getTimesText(),
ticketCount + "건"
});
}
ConsoleUI.printTable(new String[] {"번호", "영화", "상영관", "상영시간", "예매"}, rows);
int select = ConsoleInput.readInt("삭제할 일정 번호 (0. 취소) >> ", 0, pos);
if (select == 0)
{
ConsoleUI.info("삭제를 취소했습니다.");
return;
}
int index = select - 1;
MovieTimetable target = table[index];
String label = "'" + target.getMovie().getTitle() + "' / " + target.getScreen().getName();
if (ticketManager != null && ticketManager.countTickets(target.getScreen()) > 0)
{
ConsoleUI.error(label + " 일정에 예매 내역이 있어 삭제할 수 없습니다.");
ConsoleUI.info("티켓 예매 메뉴에서 해당 예매를 먼저 취소해주세요.");
return;
}
if (!ConsoleInput.confirm(label + " 상영일정을 삭제할까요?"))
{
ConsoleUI.info("삭제를 취소했습니다.");
return;
}
for (int j = index; j < pos - 1; j++)
{
table[j] = table[j + 1];
}
table[pos - 1] = null;
pos--;
ConsoleUI.success(label + " 상영일정이 삭제되었습니다.");
}
public void timetableManagerMain()
{
while (true)
{
ConsoleUI.title("상영일정 관리 (등록 " + pos + "/" + table.length + ")");
System.out.println(" 1. 상영일정 등록");
System.out.println(" 2. 상영일정 목록");
System.out.println(" 3. 상영일정 삭제");
System.out.println(" 0. 메인 메뉴로");
int input = ConsoleInput.readInt("메뉴 선택 >> ", 0, 3);
switch (input)
{
case 1:
add();
break;
case 2:
list();
break;
case 3:
remove();
break;
case 0:
return;
default:
break;
}
}
}
// ------------------------------------------------------------------ 조회용
/** 중복 없는 영화 목록 (정렬 순서 유지) */
public List<Movie> getMovies()
{
List<Movie> movies = new ArrayList<Movie>();
for (int i = 0; i < pos; i++)
{
if (!movies.contains(table[i].getMovie()))
{
movies.add(table[i].getMovie());
}
}
return movies;
}
private Movie findMovie(String title)
{
for (int i = 0; i < pos; i++)
{
if (table[i].getMovie().getTitle().equals(title))
{
return table[i].getMovie();
}
}
return null;
}
public MovieTimetable findByScreen(Screen screen)
{
for (int i = 0; i < pos; i++)
{
if (table[i].getScreen() == screen)
{
return table[i];
}
}
return null;
}
private List<Screen> getAvailableScreens()
{
List<Screen> result = new ArrayList<Screen>();
for (int i = 0; i < sm.getCount(); i++)
{
Screen sc = sm.getScreen(i);
if (findByScreen(sc) == null)
{
result.add(sc);
}
}
return result;
}
private boolean isFull()
{
return pos == table.length;
}
public int getPos()
{
return pos;
}
public int getMaxCount()
{
return table.length;
}
public MovieTimetable[] getMT()
{
return table;
}
}

Seat#

public class Seat
{
private int rowNo, seatNo;
public Seat(int rowNo, int seatNo)
{
this.rowNo = rowNo;
this.seatNo = seatNo;
}
/** 예) 3열 5번 */
@Override
public String toString()
{
return rowNo + "열 " + seatNo + "번";
}
public int getRowNo()
{
return rowNo;
}
public void setRowNo(int rowNo)
{
this.rowNo = rowNo;
}
public int getSeatNo()
{
return seatNo;
}
public void setSeatNo(int seatNo)
{
this.seatNo = seatNo;
}
}

Ticket#

public class Ticket
{
private String ticketNo;
private Movie movie;
private Screen screen;
private String playHour;
private Seat[] seats;
public Ticket(String ticketNo, Movie movie, Screen screen, String playHour, Seat seats[])
{
this.ticketNo = ticketNo;
this.movie = movie;
this.screen = screen;
this.playHour = playHour;
this.seats = seats;
}
/** 예) 3열 4번, 3열 5번 */
public String getSeatsText()
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < seats.length; i++)
{
if (i > 0)
{
sb.append(", ");
}
sb.append(seats[i]);
}
return sb.toString();
}
/** 이 티켓이 해당 상영관/시간의 해당 좌석을 차지하고 있는지 */
public boolean occupies(Screen screen, String playHour, int rowNo, int seatNo)
{
if (this.screen != screen || !this.playHour.equals(playHour))
{
return false;
}
for (Seat seat : seats)
{
if (seat.getRowNo() == rowNo && seat.getSeatNo() == seatNo)
{
return true;
}
}
return false;
}
public String getTicketNo()
{
return ticketNo;
}
public void setTicketNo(String ticketNo)
{
this.ticketNo = ticketNo;
}
public Movie getMovie()
{
return movie;
}
public void setMovie(Movie movie)
{
this.movie = movie;
}
public Screen getScreen()
{
return screen;
}
public void setScreen(Screen screen)
{
this.screen = screen;
}
public String getPlayHour()
{
return playHour;
}
public void setPlayHour(String playHour)
{
this.playHour = playHour;
}
public Seat[] getSeats()
{
return seats;
}
public void setSeats(Seat[] seats)
{
this.seats = seats;
}
}

TicketManager#

import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;
public class TicketManager
{
private static int serialCode = 10000;
private TimetableManager tm;
private Ticket[] tickets;
private int pos;
public TicketManager(int n, TimetableManager tm)
{
tickets = new Ticket[n];
this.tm = tm;
pos = 0;
}
// ------------------------------------------------------------------ 예매
public void add()
{
ConsoleUI.section("티켓 예매");
if (tm.getPos() == 0)
{
ConsoleUI.error("예매할 수 있는 상영일정이 없습니다.");
ConsoleUI.info("메인 메뉴 2번(상영일정 관리)에서 상영일정을 먼저 등록해주세요.");
return;
}
if (pos == tickets.length)
{
ConsoleUI.error("예매 가능 건수(최대 " + tickets.length + "건)를 모두 사용했습니다.");
ConsoleUI.info("기존 예매를 취소한 뒤 다시 시도해주세요.");
return;
}
// 1) 상영 회차 선택 : 영화/상영관/시간을 직접 타이핑하는 대신 번호로 고른다.
List<MovieTimetable> sessionTables = new ArrayList<MovieTimetable>();
List<String> sessionTimes = new ArrayList<String>();
List<String[]> rows = new ArrayList<String[]>();
for (int i = 0; i < tm.getPos(); i++)
{
MovieTimetable mt = tm.getMT()[i];
for (String t : mt.getTimes())
{
sessionTables.add(mt);
sessionTimes.add(t);
int remain = remainingSeats(mt.getScreen(), t);
rows.add(new String[] {
String.valueOf(sessionTables.size()),
mt.getMovie().getTitle(),
mt.getMovie().getGrade(),
mt.getScreen().getName(),
t + " ~ " + mt.getEndTime(t),
remain == 0 ? "매진" : remain + "/" + mt.getScreen().getTotalSeats()
});
}
}
ConsoleUI.printTable(new String[] {"번호", "영화", "등급", "상영관", "상영시간", "잔여석"}, rows);
MovieTimetable mt;
String time;
while (true)
{
int select = ConsoleInput.readInt("회차 번호 (0. 취소) >> ", 0, sessionTables.size());
if (select == 0)
{
ConsoleUI.info("예매를 취소했습니다.");
return;
}
mt = sessionTables.get(select - 1);
time = sessionTimes.get(select - 1);
if (remainingSeats(mt.getScreen(), time) == 0)
{
ConsoleUI.error("매진된 회차입니다. 다른 회차를 선택해주세요.");
continue;
}
break;
}
Screen screen = mt.getScreen();
// 2) 좌석 선택
System.out.println();
System.out.println(" " + mt.getMovie().getTitle() + " / " + screen.getName() + " / " + time + " ~ " + mt.getEndTime(time));
int row;
int[] seatNos;
while (true)
{
printSeatMap(screen, time, 0, null);
row = ConsoleInput.readInt("열 번호 (1~" + screen.getRowNum() + ", 0. 취소) >> ", 0, screen.getRowNum());
if (row == 0)
{
ConsoleUI.info("예매를 취소했습니다.");
return;
}
String input = ConsoleInput.readLine("좌석 번호 (예: 3 4 5 또는 3-5, 빈 입력 시 열 다시 선택) : ");
if (input.isEmpty())
{
continue;
}
try
{
seatNos = parseSeats(input, screen.getSeatNum());
} catch (IllegalArgumentException e)
{
ConsoleUI.error(e.getMessage());
continue;
}
List<Integer> taken = new ArrayList<Integer>();
for (int no : seatNos)
{
if (isReserved(screen, time, row, no))
{
taken.add(no);
}
}
if (!taken.isEmpty())
{
ConsoleUI.error(row + "열 " + joinNumbers(taken) + "번 좌석은 이미 예매되었습니다. 다른 좌석을 선택해주세요.");
continue;
}
printSeatMap(screen, time, row, seatNos);
System.out.println(" 선택한 좌석 : " + row + "열 " + joinNumbers(seatNos) + "번 (" + seatNos.length + "명)");
if (ConsoleInput.confirm("이 좌석으로 예매할까요?"))
{
break;
}
ConsoleUI.info("좌석을 다시 선택합니다.");
}
// 3) 발권
Seat[] seats = new Seat[seatNos.length];
for (int i = 0; i < seatNos.length; i++)
{
seats[i] = new Seat(row, seatNos[i]);
}
String ticketNo = "T" + serialCode;
serialCode++;
Ticket ticket = new Ticket(ticketNo, mt.getMovie(), screen, time, seats);
tickets[pos] = ticket;
pos++;
ConsoleUI.success("예매가 완료되었습니다.");
printTicket(ticket);
ConsoleUI.info("예매번호 [" + ticketNo + "]는 예매 확인/취소 시 필요하니 꼭 기억해주세요.");
}
/**
* "3 4 5", "3-5", "1,2 7-9" 형태의 입력을 좌석 번호 배열로 변환한다.
* 숫자가 아니거나, 범위를 벗어나거나, 중복되면 IllegalArgumentException 을 던진다.
*/
private int[] parseSeats(String input, int maxSeat)
{
TreeSet<Integer> result = new TreeSet<Integer>();
for (String token : input.split("[\\s,]+"))
{
int from, to;
if (token.matches("\\d{1,3}"))
{
from = to = Integer.parseInt(token);
} else if (token.matches("\\d{1,3}-\\d{1,3}"))
{
String[] parts = token.split("-");
from = Integer.parseInt(parts[0]);
to = Integer.parseInt(parts[1]);
if (from > to)
{
throw new IllegalArgumentException("'" + token + "' 범위가 잘못되었습니다. 작은 번호를 앞에 써주세요. 예) 3-5");
}
} else
{
throw new IllegalArgumentException("'" + token + "'은(는) 올바른 좌석 번호가 아닙니다. 예) 3 4 5 또는 3-5");
}
for (int no = from; no <= to; no++)
{
if (no < 1 || no > maxSeat)
{
throw new IllegalArgumentException(no + "번 좌석은 없습니다. (1 ~ " + maxSeat + "번)");
}
if (!result.add(no))
{
throw new IllegalArgumentException(no + "번 좌석이 중복 입력되었습니다.");
}
}
}
int[] arr = new int[result.size()];
int i = 0;
for (int no : result)
{
arr[i++] = no;
}
return arr;
}
/**
* 좌석 배치도 출력. [ ] 빈 좌석, [X] 예매 완료, [O] 지금 선택한 좌석
*/
private void printSeatMap(Screen screen, String time, int selectedRow, int[] selectedSeats)
{
int cols = screen.getSeatNum();
int mapWidth = 6 + cols * 3;
System.out.println();
System.out.println(" " + ConsoleUI.center("[ S C R E E N ]", mapWidth));
StringBuilder header = new StringBuilder(" ");
for (int c = 1; c <= cols; c++)
{
header.append(String.format("%2d ", c));
}
System.out.println(header);
for (int r = 1; r <= screen.getRowNum(); r++)
{
StringBuilder line = new StringBuilder(String.format(" %3d열 ", r));
for (int c = 1; c <= cols; c++)
{
if (r == selectedRow && contains(selectedSeats, c))
{
line.append("[O]");
} else if (isReserved(screen, time, r, c))
{
line.append("[X]");
} else
{
line.append("[ ]");
}
}
System.out.println(line);
}
String legend = " [ ] 예매 가능 [X] 예매 완료";
if (selectedSeats != null)
{
legend += " [O] 선택한 좌석";
}
System.out.println(legend + " (잔여 " + remainingSeats(screen, time) + "/" + screen.getTotalSeats() + "석)");
}
// ------------------------------------------------------------------ 확인
public void list()
{
ConsoleUI.section("예매 확인");
if (pos == 0)
{
ConsoleUI.info("예매 내역이 없습니다.");
return;
}
Ticket ticket = readTicket();
if (ticket != null)
{
printTicket(ticket);
}
}
public void listAll()
{
ConsoleUI.section("전체 예매 내역 (" + pos + "/" + tickets.length + ")");
if (pos == 0)
{
ConsoleUI.info("예매 내역이 없습니다.");
return;
}
List<String[]> rows = new ArrayList<String[]>();
for (int i = 0; i < pos; i++)
{
Ticket t = tickets[i];
rows.add(new String[] {
t.getTicketNo(),
t.getMovie().getTitle(),
t.getScreen().getName(),
t.getPlayHour(),
t.getSeatsText(),
t.getSeats().length + "명"
});
}
ConsoleUI.printTable(new String[] {"예매번호", "영화", "상영관", "시간", "좌석", "인원"}, rows);
}
/** 예매번호를 입력받아 티켓을 찾는다. 숫자만 입력해도(10000) 찾을 수 있다. */
private Ticket readTicket()
{
String input = ConsoleInput.readLine("예매번호 (예: T10000, 빈 입력 시 취소) : ");
if (input.isEmpty())
{
ConsoleUI.info("취소했습니다.");
return null;
}
String ticketNo = input.matches("\\d+") ? "T" + input : input;
int index = indexOf(ticketNo);
if (index < 0)
{
ConsoleUI.error("'" + input + "' 예매번호를 찾을 수 없습니다.");
ConsoleUI.info("4번(전체 예매 내역)에서 예매번호를 확인할 수 있습니다.");
return null;
}
return tickets[index];
}
private void printTicket(Ticket t)
{
String start = t.getPlayHour();
MovieTimetable mt = tm.findByScreen(t.getScreen());
String end = (mt == null) ? "" : " ~ " + mt.getEndTime(start);
String[] lines = {
"예매번호 : " + t.getTicketNo(),
"영 화 : " + t.getMovie().getTitle() + " (" + t.getMovie().getGrade() + ")",
"상 영 관 : " + t.getScreen().getName(),
"상영시간 : " + start + end,
"좌 석 : " + t.getSeatsText(),
"인 원 : " + t.getSeats().length + "명"
};
int width = 30;
for (String line : lines)
{
width = Math.max(width, ConsoleUI.displayWidth(line));
}
String border = " +" + ConsoleUI.repeat('-', width + 2) + "+";
System.out.println();
System.out.println(border);
System.out.println(" | " + ConsoleUI.pad(ConsoleUI.center("MOVIE TICKET", width), width) + " |");
System.out.println(border);
for (String line : lines)
{
System.out.println(" | " + ConsoleUI.pad(line, width) + " |");
}
System.out.println(border);
}
// ------------------------------------------------------------------ 취소
public void remove()
{
ConsoleUI.section("예매 취소");
if (pos == 0)
{
ConsoleUI.info("취소할 예매 내역이 없습니다.");
return;
}
Ticket ticket = readTicket();
if (ticket == null)
{
return;
}
printTicket(ticket);
if (!ConsoleInput.confirm("이 예매를 취소할까요?"))
{
ConsoleUI.info("예매 취소를 중단했습니다. 예매는 그대로 유지됩니다.");
return;
}
int index = indexOf(ticket.getTicketNo());
for (int j = index; j < pos - 1; j++)
{
tickets[j] = tickets[j + 1];
}
tickets[pos - 1] = null;
pos--;
ConsoleUI.success("예매번호 " + ticket.getTicketNo() + " 예매가 취소되었습니다.");
}
public void ticketManagerMain()
{
while (true)
{
ConsoleUI.title("티켓 예매 (예매 " + pos + "/" + tickets.length + ")");
System.out.println(" 1. 예매하기");
System.out.println(" 2. 예매 확인 (예매번호로 조회)");
System.out.println(" 3. 예매 취소");
System.out.println(" 4. 전체 예매 내역");
System.out.println(" 0. 메인 메뉴로");
int input = ConsoleInput.readInt("메뉴 선택 >> ", 0, 4);
switch (input)
{
case 1:
add();
break;
case 2:
list();
break;
case 3:
remove();
break;
case 4:
listAll();
break;
case 0:
return;
default:
break;
}
}
}
// ------------------------------------------------------------------ 조회용
private int indexOf(String ticketNo)
{
for (int i = 0; i < pos; i++)
{
if (tickets[i].getTicketNo().equalsIgnoreCase(ticketNo))
{
return i;
}
}
return -1;
}
/** 같은 상영관 + 같은 시간 회차에서 해당 좌석이 예매되었는지 */
public boolean isReserved(Screen screen, String time, int row, int seatNo)
{
for (int i = 0; i < pos; i++)
{
if (tickets[i].occupies(screen, time, row, seatNo))
{
return true;
}
}
return false;
}
/** 해당 회차에 예매된 좌석 수 */
public int countReserved(Screen screen, String time)
{
int total = 0;
for (int i = 0; i < pos; i++)
{
if (tickets[i].getScreen() == screen && tickets[i].getPlayHour().equals(time))
{
total += tickets[i].getSeats().length;
}
}
return total;
}
private int remainingSeats(Screen screen, String time)
{
return screen.getTotalSeats() - countReserved(screen, time);
}
/** 해당 상영관의 예매 건수 */
public int countTickets(Screen screen)
{
int total = 0;
for (int i = 0; i < pos; i++)
{
if (tickets[i].getScreen() == screen)
{
total++;
}
}
return total;
}
public int getPos()
{
return pos;
}
public int getMaxCount()
{
return tickets.length;
}
private static boolean contains(int[] arr, int value)
{
if (arr == null)
{
return false;
}
for (int v : arr)
{
if (v == value)
{
return true;
}
}
return false;
}
private static String joinNumbers(int[] numbers)
{
List<Integer> list = new ArrayList<Integer>();
for (int n : numbers)
{
list.add(n);
}
return joinNumbers(list);
}
private static String joinNumbers(List<Integer> numbers)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < numbers.size(); i++)
{
if (i > 0)
{
sb.append(", ");
}
sb.append(numbers.get(i));
}
return sb.toString();
}
}

Main#

/**
* 영화관 예매 시스템 (콘솔)
* <p>
* 사용 순서 : 1. 상영관 등록 -> 2. 상영일정(영화+시간) 등록 -> 3. 티켓 예매
* 자세한 사용법은 프로그램 실행 후 메인 메뉴 9번 또는 README.md 참고.
*/
public class Main
{
private static final int MAX_SCREENS = 5;
private static final int MAX_TIMETABLES = 5;
private static final int MAX_TICKETS = 10;
private static ScreenManager screenManager;
private static TimetableManager timetableManager;
private static TicketManager ticketManager;
public static void main(String[] args)
{
screenManager = new ScreenManager(MAX_SCREENS);
timetableManager = new TimetableManager(MAX_TIMETABLES, screenManager);
ticketManager = new TicketManager(MAX_TICKETS, timetableManager);
screenManager.setTimetableManager(timetableManager);
timetableManager.setTicketManager(ticketManager);
ConsoleUI.title("영화관 예매 시스템");
System.out.println(" 처음이라면 9번(사용 방법)을 확인하거나, 8번(샘플 데이터)으로 바로 체험해보세요.");
try
{
run();
} catch (InputClosedException e)
{
System.out.println();
ConsoleUI.info("입력이 종료되어 프로그램을 종료합니다.");
}
}
private static void run()
{
while (true)
{
printMainMenu();
int menu = ConsoleInput.readInt("메뉴 선택 >> ", 0, 9);
try
{
switch (menu)
{
case 1:
screenManager.screenManagerMain();
break;
case 2:
timetableManager.timetableManagerMain();
break;
case 3:
ticketManager.ticketManagerMain();
break;
case 8:
loadSampleData();
break;
case 9:
printHelp();
break;
case 0:
if (ConsoleInput.confirm("프로그램을 종료할까요? (입력한 데이터는 저장되지 않습니다)"))
{
ConsoleUI.info("이용해주셔서 감사합니다.");
return;
}
break;
default:
ConsoleUI.error(menu + "번 메뉴는 없습니다. 목록에 있는 번호를 입력해주세요.");
break;
}
} catch (InputClosedException e)
{
throw e;
} catch (RuntimeException e)
{
// 예상하지 못한 버그가 있어도 프로그램 전체가 죽지 않도록 메인 메뉴로 복귀
ConsoleUI.error("예기치 못한 오류가 발생했습니다: " + e);
ConsoleUI.info("메인 메뉴로 돌아갑니다. 입력한 데이터는 유지됩니다.");
}
}
}
private static void printMainMenu()
{
ConsoleUI.title("메인 메뉴");
System.out.println(" 현재 상태 : 상영관 " + screenManager.getCount() + "/" + MAX_SCREENS
+ " 상영일정 " + timetableManager.getPos() + "/" + MAX_TIMETABLES
+ " 예매 " + ticketManager.getPos() + "/" + MAX_TICKETS);
System.out.println();
System.out.println(" 1. 상영관 관리 (상영관 등록 / 목록 / 삭제)");
System.out.println(" 2. 상영일정 관리 (영화와 상영시간 등록 / 목록 / 삭제)");
System.out.println(" 3. 티켓 예매 (예매 / 예매 확인 / 예매 취소)");
System.out.println(" 8. 샘플 데이터 불러오기");
System.out.println(" 9. 사용 방법");
System.out.println(" 0. 종료");
System.out.println();
System.out.println(" " + nextStepHint());
}
/** 현재 상태에 맞춰 다음에 할 일을 안내 */
private static String nextStepHint()
{
if (screenManager.getCount() == 0)
{
return ">> 먼저 1번에서 상영관을 등록하세요. (또는 8번 샘플 데이터)";
}
if (timetableManager.getPos() == 0)
{
return ">> 다음은 2번에서 상영할 영화와 시간을 등록하세요.";
}
if (ticketManager.getPos() == 0)
{
return ">> 3번에서 티켓을 예매할 수 있습니다.";
}
return ">> 3번에서 예매 확인/취소, 추가 예매를 할 수 있습니다.";
}
private static void printHelp()
{
ConsoleUI.title("사용 방법");
String[] help = {
"[사용 순서]",
" 1) 상영관 관리 > 상영관 등록",
" 상영관 이름과 좌석 배치(열 수, 한 열의 좌석 수)를 입력합니다.",
" 2) 상영일정 관리 > 상영일정 등록",
" 영화 정보(제목, 개봉일, 러닝타임, 등급)를 입력한 뒤",
" 상영할 상영관과 시작시간을 지정합니다. 한 영화를 여러 상영관에 걸 수 있습니다.",
" 3) 티켓 예매 > 예매하기",
" 상영 회차를 번호로 고르고, 좌석 배치도를 보면서 열과 좌석을 선택합니다.",
" 예매가 끝나면 예매번호(예: T10000)가 발급됩니다. 확인/취소할 때 필요합니다.",
"",
"[입력 방법]",
" - 메뉴와 목록은 앞에 붙은 번호를 입력해 선택합니다.",
" - 번호 선택 화면에서 0 을 입력하면 취소(또는 이전 단계)입니다.",
" - 글자 입력 화면에서 아무것도 입력하지 않고 Enter 를 누르면 취소입니다.",
" - 개봉일 : 2026-09-24 또는 20260924",
" - 상영시간 : 24시간 형식, 공백으로 구분 예) 10:00 13:30 17:00",
" - 좌석 번호 : 공백으로 구분하거나 범위로 입력 예) 3 4 5 또는 3-5",
" - 예/아니오 : y 또는 n",
"",
"[규칙]",
" - 상영관 1곳에는 영화 1편만 편성할 수 있습니다.",
" - 같은 상영관의 회차끼리는 러닝타임만큼 간격이 있어야 합니다.",
" - 한 번의 예매는 같은 열의 좌석만 선택할 수 있습니다.",
" - 상영일정이 있는 상영관, 예매가 있는 상영일정은 삭제할 수 없습니다.",
" (예매 취소 -> 상영일정 삭제 -> 상영관 삭제 순서로 지웁니다)",
" - 최대 등록 수 : 상영관 " + MAX_SCREENS + "개, 상영일정 " + MAX_TIMETABLES + "개, 예매 "
+ MAX_TICKETS + "건",
" - 데이터는 파일로 저장되지 않으며 프로그램을 종료하면 사라집니다.",
"",
"[좌석 배치도 기호]",
" [ ] 예매 가능 [X] 예매 완료 [O] 지금 선택한 좌석",
};
for (String line : help)
{
System.out.println(" " + line);
}
}
private static void loadSampleData()
{
ConsoleUI.section("샘플 데이터 불러오기");
if (screenManager.getCount() > 0 || timetableManager.getPos() > 0 || ticketManager.getPos() > 0)
{
ConsoleUI.error("이미 입력된 데이터가 있어 샘플 데이터를 불러올 수 없습니다.");
ConsoleUI.info("샘플 데이터는 프로그램을 처음 실행했을 때(데이터가 비어 있을 때)만 불러올 수 있습니다.");
return;
}
Screen s1 = new Screen("1관", 5, 8);
Screen s2 = new Screen("2관", 6, 10);
Screen s3 = new Screen("3관", 4, 6);
screenManager.addScreen(s1);
screenManager.addScreen(s2);
screenManager.addScreen(s3);
Movie m1 = new Movie("인터스텔라", "2014-11-06", 169, "12세이상관람가");
Movie m2 = new Movie("기생충", "2019-05-30", 132, "15세이상관람가");
Movie m3 = new Movie("인사이드 아웃 2", "2024-06-12", 96, "전체관람가");
timetableManager.addTimetable(new MovieTimetable(m1, s1, MovieTimetable.parseTimes("10:00 13:30 17:00", 169)));
timetableManager.addTimetable(new MovieTimetable(m2, s2, MovieTimetable.parseTimes("11:00 14:00 19:30", 132)));
timetableManager.addTimetable(new MovieTimetable(m3, s3, MovieTimetable.parseTimes("09:30 12:00", 96)));
ConsoleUI.success("상영관 3개, 영화 3편, 상영일정 3개를 불러왔습니다.");
ConsoleUI.info("메인 메뉴 3번(티켓 예매)에서 바로 예매해볼 수 있습니다.");
}
}
공유하기

이 글이 도움이 되었다면 더 많은 분들께 공유해 주세요!

Java 영화관 예매 프로그램 완성본 소스코드
https://nirobase.me/posts/190517-java-movie-ticket-booking/
작성자
irostub
게시일
2019-05-17
라이선스
CC BY-NC-SA 4.0

일부 정보가 오래되었을 수 있습니다

목차