


LoginPanel: 초기 게임 접속 화면MainFrame: 게임 시작 화면. 캔버스, 채팅, 제시어, 색깔 바꾸는 패널 등 모든 패널을 가지고 있는 JFrame을 상속한 클래스GamePanel: 게임 시작 및 게임 종료 버튼이 들어있는 JPanel 상속 클래스. MainFrame에 add하게됨ColorPalette: 색을 바꿀 수 있도록 색깔 버튼이 있는 JPanel 상속 클래스.ChatPanel: 채팅 UI가 있는 JPanel 상속 클래스.CanvasPanel: 그림을 그리는 UI가 있는 JPanel 상속 클래스. 위에 제시어와 타이머 UI 존재. 아래에는 캔버스와 지우기 버튼 존재.

public class Client {
private Socket socket;
private BufferedReader in;
private PrintWriter out;
private GameController gameController;
//송수신
public void send(String msg) {
out.println(msg);
out.flush();
}
public void listen() {
Thread listen = new Thread(() -> {
String msg;
try {
while ((msg = in.readLine())!=null) {
System.out.println("[DEBUG] 클라이언트 수신함: " + msg);
gameController.processMessage(msg);
}
} catch (IOException e) {
}finally {
disconnect();
}
});
listen.start();
}
//연결
public void connect(String ip, int port){
try {
socket = new Socket(ip, port);
InputStreamReader input = new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8);
in = new BufferedReader(input);
OutputStreamWriter output = new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8);
out = new PrintWriter(output, true);
} catch (IOException e) {
e.printStackTrace();
}
}
public void disconnect() {
try {
socket.close();
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public void setGameController(GameController gameController) {this.gameController = gameController;}
}
연결 및 연결 종료 코드: 메소드 호출해서 연결 및 연결 종료
public void connect(String ip, int port){
try {
socket = new Socket(ip, port);
InputStreamReader input = new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8);
in = new BufferedReader(input);
OutputStreamWriter output = new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8);
out = new PrintWriter(output, true);
} catch (IOException e) {
e.printStackTrace();
}
}
public void disconnect() {
try {
socket.close();
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
connect(): 소켓을 새로 만들어서, 접속하려는 ip주소와 포트번호 인자로 전달.BufferedReader로 소켓의 입력을 읽어오기 `InputStreamReader`로 클라이언트 측 input을 읽어오기 OutputStreamWriter와 PrintWriter를 통해 소켓에 출력함.disconnect(): 연결 종료. 소켓, 입출력 모두 close()해서 자원 정리.listen() 스레드: 서버 측에서 보내는 내용을 소켓으로 수신하기 위해 스레드로 생성.
public void listen() {
Thread listen = new Thread(() -> {
String msg;
try {
while ((msg = in.readLine())!=null) {
System.out.println("[DEBUG] 클라이언트 수신함: " + msg);
gameController.processMessage(msg);
}
} catch (IOException e) {
}finally {
disconnect();
}
});
listen.start();
}
while문을 통해 읽어들여 내용이 있으면(= null이 아니면), 해당 내용을 컨트롤러로 보내서 파싱 등 분석.
GameController
public class GameController {
private Client client;
private final CommandFactory commandFactory = CommandFactory.getInstance();
private ViewController viewController;
//게임 접속-종료
public void accessGame(String ip, String name) {
ClientTest.startGame(ip);
client.send("NAME:"+name);
}
public void noticeStart() {
client.send("START:");
}
public void exitRoom() {
viewController.endPanel();
System.out.println("서버와의 연결이 종료됩니다.");
client.disconnect();
System.exit(0);
}
//메시지 수신(파싱)
public void processMessage(String msg) {
commandFactory.createCommand(viewController,msg);
}
//메시지 전송
public void sendChat(String msg) {
String sendMessage = "CHAT:"+msg;
client.send(sendMessage);
}
public void sendErase() {
client.send("ERASE:");
}
public void sendDrawing(Point from, Point to) {
String msg = String.format("DRAW:%d:%d:%d:%d", from.x, from.y, to.x, to.y);
client.send(msg);
}
public void sendColor(String colorCode) {client.send("COLOR:"+colorCode);}
//Setter
public void setViewController(ViewController viewController) {this.viewController = viewController;}
public void setClient (Client client) {this.client = client;}
}
ViewController
public class ViewController {
private MainFrame mainFrame;
public void updateDrawState (boolean state) {
if (state) {
mainFrame.enableDrawing();
mainFrame.disableChatting();
}
else {
mainFrame.disableDrawing();
mainFrame.enableChatting();
}
}
//게임 시작-종료 시 화면 세팅
public void startPanel() {
this.mainFrame.enablePanel();
this.mainFrame.disableStartButton();
}
public void endPanel() {
this.mainFrame.dispose();
}
//화면 업데이트
public void updateKeyWord(String keyword) {
mainFrame.updateKeyWord(keyword);
}
public void updateCanvasPanel(Point from, Point to) {mainFrame.updateCanvas(from, to);}
public void eraseCanvasPanel() {mainFrame.eraseCanvas();}
public void updateChatPanel(String msg) {
mainFrame.updateTextArea(msg);
}
public void updateTimer(int time) {
mainFrame.updateTimer(time);
}
public void updateCurrentColor(String colorCode) {mainFrame.updateCurrentColor(colorCode);}
//Setter
public void setMainFrame (MainFrame mainFrame) {this.mainFrame = mainFrame;}
}
execute라는 메소드 생성.public interface Command {
public void execute(ViewController viewController, String msg);
}
public class ChatCommand implements Command {
@Override
public void execute(ViewController viewController, String data) {
viewController.updateChatPanel(data);
}
}
public class DrawCommand implements Command {
@Override
public void execute(ViewController viewController, String msg) {
String[] tokens = msg.split(":");
Point from = new Point(Integer.parseInt(tokens[0]),Integer.parseInt(tokens[1]));
Point to = new Point(Integer.parseInt(tokens[2]),Integer.parseInt(tokens[3]));
viewController.updateCanvasPanel(from, to);
}
}
public class CommandFactory {
private static CommandFactory instance = null;
Map<String, Command> commandMap = new HashMap<>();
private CommandFactory() {
commandMap.put("DRAW", new DrawCommand());
commandMap.put("CHAT", new ChatCommand());
commandMap.put("ERASE", new EraseCommand());
commandMap.put("START", new StartCommand());
commandMap.put("DRAWSTATE", new DrawStateCommand());
commandMap.put("KEYWORD", new KeywordCommand());
commandMap.put("COLOR", new ColorCommand());
commandMap.put("TIMER", new TimerCommand());
}
public static CommandFactory getInstance() {
if (instance == null) {
instance = new CommandFactory();
}
return instance;
}
public void createCommand(ViewController viewController, String msg) {
String[] tokens = msg.split(":",2);
String data;
if (tokens.length > 1) data = tokens[1];
else data = null;
Command commandProcessor = commandMap.get(tokens[0]);
if (commandProcessor == null) viewController.updateChatPanel(msg);
else commandProcessor.execute(viewController, data);
}
}Map을 사용해서 들어온 문자열(헤더)에 매칭되는 종류의 커맨드 생성.createCommand(): 들어온 문자열을 split한 후, 헤더(=첫 번째 문자열)를 보고 관련 커맨드 생성. 맵에서 매핑되는 커맨드 생성.execute 메소드 호출해서 관련 로직 실행.
public class Server extends Thread{
private ServerSocket serverSocket;
private final List<GameRoom> gameRooms = new ArrayList<>();
private GameService gameService;
private CheckAnswerService checkAnswerService;
private DrawerService drawerService;
private GameWordService gameWordService;
private QuizWordRepository quizWordRepository;
public Server(){
this.quizWordRepository = new QuizWordRepository();
this.checkAnswerService = new CheckAnswerService();
this.drawerService = new DrawerService();
this.gameWordService = new GameWordService();
this.gameService = new GameService(this.drawerService, this.gameWordService,
this.checkAnswerService, this.quizWordRepository);
}
public void run() {
makeGameRoom();
try {
serverSocket = new ServerSocket(50023);
while (true) {
System.out.println("서버 연결 대기중 ....");
Socket socket = serverSocket.accept();
System.out.println(socket.getInetAddress().getHostAddress()+"와 연결되었습니다.");
addPlayer(socket);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void addPlayer(Socket socket) {
GameRoom gameRoom = gameRooms.get(0);
ConnectionController handler = new ConnectionController(socket, this);
handler.setGameRoom(gameRoom);
handler.start();
}
public void makeGameRoom() {
GameRoom newGameRoom = new GameRoom(gameService);
this.gameRooms.add(newGameRoom);
}
}
클래스 생성 시 생성자에서 게임 진행에 필요한 서비스 클래스들과 레포지토리 클래스 생성 서버 클래스의 필드에 저장
스레드의 run():
accept() 해준 뒤, 해당 클라이언트와의 연결 소켓을 인자로 넘겨서 게임룸에 플레이어 추가addPlayer(): 게임룸에 플레이어 추가.
execute 메소드의 내부만 변경
: 현재 게임룸과 게임 컨트롤러의 구분이 조금 애매한 부분이 존재
public class GameRoom {
private final List<Player> players = new ArrayList<>();
private String currentWord;
private Player drawer;
Timer gameTimer;
private int remainingTime = 30;
Map<Player, Integer> scoreBoard = new HashMap<>();
private final GameService gameService;
private final CommandFactory commandFactory = CommandFactory.getInstance();
public GameRoom(GameService gameService) {
this.gameService = gameService;
}
//게임룸에 플레이어 추가/삭제 로직
public void addPlayer(Player p) {
players.add(p);
scoreBoard.put(p, 0); // 점수 초기값 = 0
if(drawer == null) {
drawer = p; // 첫번째로 들어오는 사람 자동으로 drawer 배정
}
}
public void removePlayer(Player p) {
players.remove(p);
scoreBoard.remove(p);
// drawer가 나간 경우 다음 drawer 선택
if (drawer != null && drawer.equals(p)) {
if (players.isEmpty()) {
drawer = null;
} else {
drawer = gameService.selectNextDrawer(this);
}
}
broadcastToRoom(p.getName()+"님이 방을 나가셨습니다.");
System.out.println(p.getName()+"님이 방을 나가셨습니다.");
}
//메시지 처리 관련 로직
//들어오는 메시지 1차 처리(각 서비스로 전달)
public void processMessage (Player player, String msg) {
commandFactory.createCommand(this, msg, player);
}
public void broadcastToRoom (String msg) {
for(Player p : players) {
p.sendMessage(msg);
}
}
//getter & setter
...
// 타이머 관련 메소드
public void startTimer() {
// 기존 타이머가 있으면 취소
if (gameTimer != null) {
gameTimer.cancel();
}
remainingTime = 30;
gameTimer = new Timer();
gameTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if (remainingTime > 0) {
broadcastToRoom("TIMER:" + remainingTime);
remainingTime--;
} else {
// 시간 종료 시 다음 라운드로
gameTimer.cancel();
broadcastToRoom("[System] 시간이 종료되었습니다!");
gameService.nextRound(GameRoom.this);
}
}
}, 0, 1000); // 0초 후 시작, 1초마다 실행
}
public void stopTimer() {
if (gameTimer != null) {
gameTimer.cancel();
gameTimer = null;
}
}
}
: 클라이언트와 연결된 소켓 등의 관리. 클라이언트 측의 송수신 작업 처리.
public class ConnectionController extends Thread implements MessageSender {
public Socket socket;
private Server server;
private BufferedReader in;
private PrintWriter out;
private GameRoom gameRoom;
private Player player;
//생성자: 입출력 설정 | 플레이어 만들기 (컨트롤러랑 1:1 대응이라..?) + 플레이어 게임룸에 추가해주기
public ConnectionController(Socket socket, Server server) {
this.socket = socket;
this.server = server;
try {
InputStreamReader input = new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8);
in = new BufferedReader(input);
OutputStreamWriter output = new OutputStreamWriter(socket.getOutputStream(),StandardCharsets.UTF_8);
out = new PrintWriter(output, true);
} catch (IOException e) {
e.printStackTrace();
}
//플레이어 설정해주기
this.player = new Player();
player.setMessageSender(this);
}
//클라이언트가 보낸게 이쪽의 in으로 들어와서, 브로드캐스트(다른 클라이언트들)한테 보내짐
public void run() {
String msg;
try {
while ((msg=in.readLine()) != null) {
gameRoom.processMessage(this.player, msg);
}
} catch (IOException e) {
e.printStackTrace();
System.out.println(player.getName()+"님의 연결이 종료되었습니다.");
} finally {
if (gameRoom != null) {
gameRoom.removePlayer(this.player);
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//서버의 브로드캐스트 메소드에서 이 메소드 호출돼서 각 클라이언트들한테 재전송됨
public void send(String msg) {
System.out.println(msg);
out.println(msg);
}
//getter&setter
public void setGameRoom(GameRoom gameRoom) {
this.gameRoom = gameRoom;
this.gameRoom.addPlayer(player);
}
public Player getPlayer() {return player;}
public void setPlayer(Player player) {this.player = player;}
}
run(): 스레드 실행 시. 클라이언트 측에서 메시지를 수신하게 되면 파싱 후 처리. 클라이언트 연결 종료 시 관련 메소드 호출해서 처리.send(): 보내야할 메시지를 클라이언트에게 송신. 게임룸(=GameController)의 브로드캐스트 메소드에서 이 메소드를 호출해서 처리.: 게임 진행에 관련된 로직을 처리하기 위한 가장 위의 컨트롤러로 세부 로직을 처리하는 서비스들을 필드로 둠
public class GameService {
private static final int SCORE_PER_ANSWER = 10;
private final DrawerService drawerService;
private final GameWordService gameWordService;
private final CheckAnswerService checkAnswerService;
private final QuizWordRepository quizWordRepository;
public GameService(DrawerService drawerService, GameWordService gameWordService,
CheckAnswerService checkAnswerService, QuizWordRepository quizWordRepository) {
this.drawerService = drawerService;
this.gameWordService = gameWordService;
this.checkAnswerService = checkAnswerService;
this.quizWordRepository = quizWordRepository;
}
public void startGame(GameRoom gameRoom) {
gameRoom.broadcastToRoom("[System] 게임이 시작되었습니다!");
nextRound(gameRoom);
}
public void checkAnswer(GameRoom room, Player sender, String data) {
String message = "CHAT:";
if (checkAnswerService.correctAnswer(room, sender, data)) {
nextRound(room);
}
message += "[" + sender.getName() + "] "+data;
room.broadcastToRoom(message);
}
public int getPlayerScore(GameRoom gameRoom, Player player) {
Integer score = gameRoom.getScoreBoard().get(player);
if (score == null) {
return 0;
}
return score;
}
// 다음 라운드 준비
public void nextRound(GameRoom gameRoom) {
gameRoom.stopTimer();
// 다음 그림 그리는 사람 선택
Player newDrawer = drawerService.selectNextDrawer(gameRoom.getPlayers(), gameRoom.getDrawer());
if (newDrawer == null) {
gameRoom.broadcastToRoom("[System] 플레이어가 없어 게임을 진행할 수 없습니다.");
return;
}
gameRoom.setDrawer(newDrawer);
gameRoom.broadcastToRoom("ERASE:");
// 사용자 업데이트
drawerService.updatePlayerStates(gameRoom, newDrawer);
// 제시어 변경
changeWord(gameRoom);
for (Player p : gameRoom.getPlayers()) {
if (!p.equals(gameRoom.getDrawer()))
p.sendMessage("[System] 새로운 라운드가 시작되었습니다!");
}
gameRoom.broadcastToRoom("다음 그림 그리는 사람은 "+newDrawer.getName()+"님 입니다.");
gameRoom.startTimer();
}
public Player selectNextDrawer(GameRoom room) {
return drawerService.selectNextDrawer(room.getPlayers(), room.getDrawer());
}
// 제시어 바꾸기
private void changeWord(GameRoom gameRoom) {
String nextWord = gameWordService.changeWord(gameRoom, quizWordRepository);
gameRoom.setCurrentWord(nextWord);
}
}
CheckAnswerService에 위임해서 처리DrawerService에 다음 그림을 그릴 사람을 고르는 로직과 플레이어의 상태(State) 업데이트 동작을 위임해서, 해당 클래스의 메소드를 호출해서 처리GameWordService에 다음 제시어를 고르는 로직을 위임해서, 해당 클래스의 메소드를 호출해서 처리 ⇒ 해당 부분은 클래스 내에서도 메소드로 만들어서 한 번 더 분리함: 그림을 그릴 사람 (=출제자)를 선정하고, 관련해서 플레이어들의 상태를 업데이트하기 위한 서비스
public class DrawerService {
public Player selectNextDrawer(List<Player> players, Player drawer) {
if (players.isEmpty()) {
return null;
}
if (drawer == null) {
return players.get(0);
}
int currentIndex = players.indexOf(drawer);
if (currentIndex == -1)
return players.get(0);
return players.get((currentIndex + 1) % players.size());
}
public void updatePlayerStates(GameRoom gameRoom, Player newDrawer) {
for(Player p: gameRoom.getPlayers()){
if(p.equals(newDrawer)) {
p.setState(new DrawingState());
p.sendMessage("DRAWSTATE:true");
} else {
p.setState(new AnsweringState());
p.sendMessage("DRAWSTATE:false");
p.sendMessage("KEYWORD:???");
}
}
}
}
selectNextDrawer(): 해당 게임룸에 있는 플레이어들의 리스트와 현재 출제자를 인자로 받아 다음 출제라를 선정해서 반환. 반환값은 GameService에서 받아 업데이트 등 처리updatePlayerStates: 해당 게임룸에 존재하는 플레이어들의 상태를 바뀐 역할에 따라 업데이트하도록 클라이언트들에게 메시지 송신: 플레이어들이 보낸 채팅이 제시어가 맞는지 확인하고 점수를 올리는 등의 작업을 위한 서비스
public class CheckAnswerService {
private static final int SCORE_PER_ANSWER = 10;
public boolean correctAnswer(GameRoom gameRoom, Player player, String msg) {
String correctWord = gameRoom.getCurrentWord();
if (msg.equalsIgnoreCase(correctWord)) {
gameRoom.broadcastToRoom("[System] " + player.getName() + "님이 정답을 맞추셨습니다! (+" + SCORE_PER_ANSWER + "점)");
addScore(gameRoom, player);
return true;
}
else {
return false;
}
}
public int getPlayerScore(GameRoom gameRoom, Player player) {
Integer score = gameRoom.getScoreBoard().get(player);
if (score == null) {
return 0;
}
return score;
}
public void addScore(GameRoom gameRoom, Player player) {
Map<Player, Integer> board = gameRoom.getScoreBoard();
Integer currentScore = board.get(player);
if (currentScore == null)
currentScore = 0;
board.put(player, board.get(player) + SCORE_PER_ANSWER);
}
}
checkAnswer(): 제시어가 맞는지 확인하고 이에 따른 boolean 반환.GameService에서 이에 해당하는 서비스들을 호출해 실행: 다음 제시어를 받아오기 위한 서비스
public class GameWordService {
public String getNewQuizWord(QuizWordRepository quizWordRepository) {
String quizWord = quizWordRepository.getRandomWord();
return quizWord;
}
public String changeWord(GameRoom gameRoom, QuizWordRepository quizWordRepository) {
String nextWord = getNewQuizWord(quizWordRepository);
gameRoom.setCurrentWord(nextWord);
System.out.println("[DEBUG] 현재 그림 그리는 사람: " + gameRoom.getDrawer().getName());
System.out.println("[DEBUG] 선정된 단어: " + nextWord);
if (gameRoom.getDrawer() != null) {
gameRoom.getDrawer().sendMessage("KEYWORD:" + nextWord);
System.out.println("[DEBUG] 서버 -> 클라이언트 전송 완료: KEYWORD:" + nextWord);
} else {
System.out.println("[DEBUG] 그림 그리는 사람이 없어서 전송 못함");
}
return nextWord;
}
QuizWordRepository에서 제시어를 받아와서 다음 제시어를 반환해주는 메소드QuizWordRepository와 DB 연동이 이루어지지 않아 우선 하드코딩된 리스트에서 제시어를 가져와서 반환public class Player {
private String name;
private String id;
private String password;
private MessageSender messageSender;
private PlayerState state;
private int score=0;
public Player() {
// default: 답맞추기
this.state=new AnsweringState();
}
public Player(String name, String id, String password) {
this.name = name;
this.id = id;
this.password = password;
this.state=new AnsweringState();
}
public void sendMessage(String msg) {
if (messageSender != null) {
messageSender.send(msg);
} else {
System.err.println("[WARNING] " + name + "의 messageSender가 null입니다. 메시지 전송 실패: " + msg);
}
}
//Getter & Setter
...
}
public interface MessageSender {
void send (String msg);
}public interface PlayerState{
boolean canDraw();
boolean canAnswer();
}
public class AnsweringState implements PlayerState {
@Override
public boolean canDraw() { return false; } // 답 맞추는 사람은 그림 못그림
@Override
public boolean canAnswer() { return true; }
}
public class DrawingState implements PlayerState {
@Override
public boolean canDraw() { return true; }
@Override
public boolean canAnswer() { return false; } // 화가는 정답 못맞춤
}
public class QuizWordRepository {
private final List<String> wordList = Arrays.asList(
"사과", "바나나", "자동차", "비행기", "컴퓨터",
"축구", "피아노", "고양이", "강아지", "아이스크림",
"우산", "시계", "안경", "모자", "자전거"
);
public String getRandomWord(){
int randomIndex = new Random().nextInt(wordList.size());
return wordList.get(randomIndex);
}
}
Timer 타이머 기능 작업 중: 대부분은 구조의 변경. SOLID 원칙 혹은 디자인 패턴 적용 시도이고 일부 기능이 추가됨.
1.기본적인 게임로직 구현: 제시어가 랜덤하게 변경되고, 다음 출제자(=그림 그리는 사람) 선정 가능

2.색 변경 기능: 캔버스에 그림을 그릴 때 색을 변경할 수 있도록 기능 추가

커맨드 인터페이스 생성: 공통 메소드인 execute() 선언
커맨드 인터페이스를 구체적으로 구현하는 클래스들을 클라이언트-서버 사이 메시지의 종류마다 새롭게 만들어서 사용
수신한 메시지의 파싱은 팩토리 패턴 일부와 맵을 사용해서 헤더(=메시지의 첫 번째 split되는 문자열)에 따라 매핑되는 구체적인 커맨드 클래스를 생성하고 이를 실행(execute)하도록 구현
public class CommandFactory {
private static CommandFactory instance = null;
Map<String, Command> commandMap = new HashMap<>();
private CommandFactory() {
commandMap.put("DRAW", new DrawCommand());
commandMap.put("NAME", new NameCommand());
commandMap.put("CHAT", new ChatCommand());
commandMap.put("ERASE", new EraseCommand());
commandMap.put("START", new StartCommand());
commandMap.put("COLOR", new ColorCommand());
}
public static CommandFactory getInstance() {
if (instance == null) {
instance = new CommandFactory();
}
return instance;
}
public void createCommand(GameController gameController, String msg, Player player) {
String[] tokens = msg.split(":");
Command commandProcessor = commandMap.get(tokens[0]);
commandProcessor.create(msg, player);
commandProcessor.execute(gameController, player);
}
}
이전 구조
현재 구조
- 변경 이유: GameService 하나의 클래스에서 담당하는 역할들과 메소드들이 점점 늘어나서 비슷한 기능들끼리 최대한 분리 → SRP(단일 책임 원칙)을 나름 고려…?
public class GameController {
private Client client;
private final CommandFactory commandFactory = CommandFactory.getInstance();
private ViewController viewController;
...
//Setter
public void setViewController(ViewController viewController) {this.viewController = viewController;}
public void setClient (Client client) {this.client = client;}
}public class GameController {
private final List<Player> players = new ArrayList<>();
private String currentWord;
private Player drawer;
Timer gameTimer;
private int remainingTime = 30;
Map<Player, Integer> scoreBoard = new HashMap<>();
private final GameService gameService;
private final CommandFactory commandFactory = CommandFactory.getInstance();
public GameController(GameService gameService) {
this.gameService = gameService;
}
...
}