Небольшое вступление
Я попал в мир IT относительно недавно: всего два года я занимаюсь разработкой приложений под iOS. Но кроме Objective-C и Swift меня всегда манил мир Java и C#. Периодически я выделял время, чтоб посмотреть какие-то видео, обучающие основам этих языков, но дальше простого просмотра и переписывания кода с экрана дело не заходило. И тут я вспомнил об одной математической игре, которую мне однажды посоветовал мой друг.
Суть игры заключается в следующем: вы идете по улице и смотрите на автомобильные номера. И в каждом номере считаете сумму всех цифр (например, в номере 8037 нужно посчитать 8 + 0 + 3 + 7). Это самый простой уровень игры. Второй по сложности уровень — посчитать сумму первой половины номера и второй (80 + 37). Есть еще третий уровень — умножить все цифры (нули при этом пропустив: 8 х 3 х 7) и четвертый — умножить первую половину номера на вторую (80 х 37).
В общем: эту игру (в консольном варианте) я и решил написать на четырех языках: Swift, Objective-C, Java и C#. Что из этого получилось? Давайте посмотрим.
С чего начнем?
Начнем мы с создания пустых проектов под консольные приложения: для Swift и Objective-C будем использовать Xcode, для Java — естественно IntelliJ IDEA, а для C# — Xamarin Studio.
Первым делом напишем вспомогательный класс GameStart
. Он у нас будет заниматься запросом ответа от пользователя пока тот не введет ключевое слово для выхода из приложения.
Swift
Создадим сам класс:
class GameStart {
}
В нем у нас будет свойство exitWord
и инициализатор:
private var exitWord: String
init(with exitWord: String) {
self.exitWord = exitWord
}
Это и будет наше ключевое слово.
Также в нем будет метод startGame
который будет постоянно спрашивать у пользователя ответ, пока тот не введет слово для выхода:
func startGame() {
print(GreetingMessage.replacingOccurrences(of: ExitWordPlaceholder, with: self.exitWord))
guard let inputWord = readLine() else {
print(ErrorMessage)
return
}
self.check(inputWord: inputWord)
}
private func check(inputWord: String) {
if inputWord == self.exitWord {
print(GoodByeMessage)
} else {
print(InputAcceptMessage.replacingOccurrences(of: InputWordPlaceholder, with: inputWord))
startGame()
}
}
Как видите, метод startGame
приветствует пользователя, затем считывает из командной строки то что ввел пользователь, и передает полученную строку в метод check(inputWord:)
.
Строковые константы, которые были использованы:
private let ExitWordPlaceholder = "{exitWord}"
private let InputWordPlaceholder = "{inputWord}"
private let GreetingMessage = "Please enter your answer (enter \"\(ExitWordPlaceholder)\" for exit):"
private let InputAcceptMessage = "You entered \"\(InputWordPlaceholder)\".\n"
private let GoodByeMessage = "Good bye.\n"
private let ErrorMessage = "There is unknown error, sorry. Good bye.\n"
Наш класс готов, теперь нужно создать объект и вызвать метод startGame()
:
let gameStart = GameStart(with: "quit")
gameStart.startGame()
В консоли это выглядит примерно вот так:
Теперь напишем этот же класс на Objective-C:
// файл заголовка GameStart.h
@interface GameStart : NSObject
- (instancetype)initWithExitWord:(NSString *)exitWord;
- (void)startGame;
@end
// файл реализации GameStart.m
const NSString *GreetingMessage = @"Please enter your answer (enter \"%@\" for exit):";
const NSString *InputAcceptMessage = @"You entered \"%@\".\n";
const NSString *GoodByeMessage = @"Good bye.\n";
const NSString *ErrorMessage = @"There is unknown error, sorry. Good bye.\n";
@interface GameStart()
@property (strong, nonatomic) NSString *exitWord;
@end
@implementation GameStart
- (instancetype)initWithExitWord:(NSString *)exitWord {
self = [super init];
if (self) {
self.exitWord = exitWord;
}
return self;
}
- (void)startGame {
NSLog(GreetingMessage, self.exitWord);
NSString *inputWord = [self readLine];
if (inputWord) {
[self checkInputWord:inputWord];
} else {
NSLog(@"%@", ErrorMessage);
}
}
- (void)checkInputWord:(NSString *)inputWord {
if ([inputWord isEqualToString:self.exitWord]) {
NSLog(@"%@", GoodByeMessage);
} else {
NSLog(InputAcceptMessage, inputWord);
[self startGame];
}
}
- (NSString *)readLine {
char inputValue;
scanf("%s", &inputValue);
return [NSString stringWithUTF8String:&inputValue];
}
@end
Ну и создание объекта с вызовом метода:
GameStart *gameStart = [[GameStart alloc] initWithExitWord:@"quit"];
[gameStart startGame];
Дальше у нас на очереди Java.
Класс GameStart
:
public class GameStart {
private static final String GreetingMessage = "Please enter your answer (enter \"%s\" for exit):";
private static final String InputAcceptMessage = "You entered \"%s\".\n";
private static final String GoodByeMessage = "Good bye.\n";
private String exitWord;
public GameStart(String exitWord) {
this.exitWord = exitWord;
}
void startGame() {
System.out.println(String.format(GreetingMessage, exitWord));
String inputWord = readLine();
checkInputWord(inputWord);
}
private void checkInputWord(String inputWord) {
if (inputWord.equals(exitWord)) {
System.out.println(GoodByeMessage);
} else {
System.out.println(String.format(InputAcceptMessage, inputWord));
startGame();
}
}
private String readLine() {
java.util.Scanner scanner = new java.util.Scanner(System.in);
return scanner.next();
}
}
И вызов:
GameStart gameStart = new GameStart("quit");
gameStart.startGame();
И завершает четверку лидеров C#
Класс:
public class GameStart
{
const string GreetingMessage = "Please enter your answer (enter \"{0}\" for exit):";
const string InputAcceptMessage = "You entered \"{0}\".\n";
const string GoodByeMessage = "Good bye.\n";
readonly string exitWord;
public GameStart(string exitWord)
{
this.exitWord = exitWord;
}
public void startGame()
{
Console.WriteLine(string.Format(GreetingMessage, exitWord));
string inputWord = Console.ReadLine();
checkInputWord(inputWord);
}
void checkInputWord(string inputWord)
{
if (inputWord.Equals(exitWord))
{
Console.WriteLine(GoodByeMessage);
}
else
{
Console.WriteLine(string.Format(InputAcceptMessage, inputWord));
startGame();
}
}
}
Вызов:
GameStart gameStart = new GameStart("quit");
gameStart.startGame();
Немного рандома не повредит
Также добавим в проект вспомогательный класс, который будет формировать наш автомобильный номер (номер должен состоять из четырех случайных цифр). Сам класс назовем просто — Randomizer
.
Swift:
class Randomizer {
var firstNumber: UInt32
var secondNumber: UInt32
var thirdNumber: UInt32
var fourthNumber: UInt32
init() {
self.firstNumber = arc4random() % 10
self.secondNumber = arc4random() % 10
self.thirdNumber = arc4random() % 10
self.fourthNumber = arc4random() % 10
}
}
Objective-C:
// файл заголовка Randomizer.h
@interface Randomizer : NSObject
@property (assign, nonatomic) NSInteger firstNumber;
@property (assign, nonatomic) NSInteger secondNumber;
@property (assign, nonatomic) NSInteger thirdNumber;
@property (assign, nonatomic) NSInteger fourthNumber;
@end
// файл реализации Randomizer.m
@implementation Randomizer
- (instancetype)init
{
self = [super init];
if (self) {
self.firstNumber = arc4random() % 10;
self.secondNumber = arc4random() % 10;
self.thirdNumber = arc4random() % 10;
self.fourthNumber = arc4random() % 10;
}
return self;
}
@end
Java:
public class Randomizer {
private static Random random = new Random();
int firstNumber;
int secondNumber;
int thirdNumber;
int fourthNumber;
public Randomizer() {
firstNumber = random.nextInt(10);
secondNumber = random.nextInt(10);
thirdNumber = random.nextInt(10);
fourthNumber = random.nextInt(10);
}
}
C#:
public class Randomizer
{
static readonly Random random = new Random();
public int firstNumber;
public int secondNumber;
public int thirdNumber;
public int fourthNumber;
public Randomizer()
{
firstNumber = random.Next(10);
secondNumber = random.Next(10);
thirdNumber = random.Next(10);
fourthNumber = random.Next(10);
}
}
Как видите, при инициализации мы просто заполняем четыре поля в классе случайными целыми числами от 0 до 9.
Универсальность наше все
Несмотря на то, что в нашем приложении будет всего одна игра, мы сделаем вид, что предусматриваем расширяемость приложения в будущем. Поэтому добавим интерфейс (для Swift
и Objective-C
— протокол) Game
с методами greet(with exitWord: String)
и check(userAnswer: String)
.
Swift:
protocol Game {
func greet(with exitWord: String)
func check(userAnswer: String)
}
Objective-C:
@protocol Game <NSObject>
- (void)greetWithExitWord:(NSString *)exitWord;
- (void)checkUserAnswer:(NSString *)userAnswer;
@end
Java:
public interface Game {
void greet(String exitWord);
void checkUserAnswer(String userAnswer);
}
C#:
public interface IGame
{
void greet(string exitWord);
void checkUserAnswer(string userAnswer);
}
На этом первую часть я закончу. Реализацию самой игры с выбором уровня сложности, проверкой ответа игрока на корректность, с блэкджеком и ... мы сделаем во второй части. Всем добра. :)
Комментарии (26)
IGR2014
27.04.2017 21:49+1Раньше я заходил на хабр почитать классные большие статьи с кучей технических подробностей о том чего я не знал. А теперь захожу посмотреть на программку на 4-х языках, которая пока что просто выводит номер, а в дальнейшем будет складывать и умножать его числа. Мда.
Вам конечно успеха, но практического смысла я не увидел. Совсем. Извините.fareloz
02.05.2017 12:38Очень печально, что люди воспринимают Хабр как площадку исключительно для профессионалов. По мне так вариативность материала на разный вкус и уровень — это преимущество, а не недостаток.
IGR2014
05.05.2017 15:01Простите, а мне, в свою очередь, очень печально что Хабр воспринимают как площадку, где можно публиковать статьи, место которым на первых страницах учебников по программированию. Там где знакомство с операторами языка.
fareloz
05.05.2017 15:14Дело в том, что «сложность» статьи — это субъективное оценочное суждение. Всегда найдется тот, кто посчитает статью любой сложности «легкой и недостойной». Именно поэтому существуют механизмы:
— оценочные: Вы можете проголосовать за статью негативно или позитивно
— фильтрационные: выбрать лучшее, все подряд и так далее
— подписки-отписки.
Так что если Вы прочитали эту статью, то тут есть выбор из двух причин:
— Вы не умеете фильтровать контент
— Общее мнение хабра-сообщества положительно в противовес вашему единичному отрицательному.
s_suhanov
02.05.2017 19:02Я не понимаю зачем же вы заходите посмотреть на эту программку? Разве я ввел вас в заблуждение текстом до ката?
IGR2014
05.05.2017 15:04Если честно, то да. Из вашего названия я решил что у вас будет распознавание этих самых номеров с камеры (заходил через ВК, так что не увидел в каких хабах размещён пост).
ruslanfedoseenko
28.04.2017 08:59+1как улучшение хотел бы посоветовать хранить номер в short int(16 bit) и цифры номера получать в геттерах с помощью деления и остатков от деления. Как по мне 4 int слишком толсто для для 4 цифр
pfemidi
Я правильно понял что startGame вызывает checkInputWord, а checkInputWord при неправильном вводе вызывает startGame, а startGame вызывает checkInputWord, а checkInputWord при неправильном вводе вызывает startGame и т.д. и смысл игры в том, чтобы покрашиться по переполнению стека? :-)
Adium
s_suhanov
Ну да, если найдется юзер, который за один сеанс введет пару миллионов ответов подряд, то для него смысл игры будет именно в этом. :)
Prototik
Пару миллионов? В JRE размер стека по-умолчанию — 512 килобайт. Если мы возьмём ОЧЕНЬ оптимистичный расчёт в 16 байтов на фрейм (два указателя на x86_64, мизер), то в стек поместится ~ 32k вызовов, или 16k ваших чудесных итераций. Хоть этот предел и сложно достичь, но мне всё-таки кажется, что код стоит переписать.
s_suhanov
Вот с вашим комментарием по делу очень согласен. Подскажете как именно переписать?
Prototik
Ну что-то в таком духе:
s_suhanov
Цикл вместо рекурсии — ваша правда. Наверное, можно даже
checkInputWord(inputWord)
вынести в условие цикла, а перед циклом сделатьString inputWord = readLine()
, чтоб избежатьwhile (true)
. Спасибо вам за адекватный ответ.Prototik
Вместо дублирования readLine можно использовать цикл do {} while (); Не уверен, что аналогичные конструкции есть в других языках, но в Java есть.
s_suhanov
Точно. Так правильнее всего. Спасибо.
tfs_cradle
public void startGame()
{
while(!quit){
…
}
}
s_suhanov
Вы, кстати, свое предположение подтвердили экспериментом? Удалось добиться краша? :)