Skip to content

Commit 113aa5f

Browse files
committed
start of CLI implementation
1 parent fec4d58 commit 113aa5f

File tree

5 files changed

+343
-3
lines changed

5 files changed

+343
-3
lines changed

common/src/commonMain/kotlin/com/willowtreeapps/common/repo/KtorProfilesRepository.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ class ProfileListHolder(val profiles: List<Profile>)
6666

6767
class ProfileListHolderSerializer : KSerializer<ProfileListHolder> {
6868

69-
override val descriptor = object : SerialClassDescImpl("Inner") {
70-
}
69+
override val descriptor = object : SerialClassDescImpl("Inner") {}
7170

7271
override fun deserialize(input: Decoder): ProfileListHolder {
7372
val list = input.decodeSerializableValue(Profile.serializer().list)

jvmcli/build.gradle

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
apply plugin: 'application'
2+
apply plugin: 'kotlin'
3+
4+
dependencies {
5+
implementation fileTree(dir: 'libs', include: ['*.jar'])
6+
implementation project(':common')
7+
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
8+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
9+
implementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.26'
10+
implementation "io.ktor:ktor-client-okhttp:$ktorVersion"
11+
implementation "io.ktor:ktor-client-json-jvm:$ktorVersion"
12+
}
13+
14+
task runMain(type: JavaExec) {
15+
main = 'com.willowtreeapps.namegame.jvmcli.Main'
16+
classpath = sourceSets.main.runtimeClasspath
17+
}
18+
19+
run {
20+
systemProperties = System.properties
21+
}
22+
23+
24+
sourceCompatibility = "8"
25+
targetCompatibility = "8"
26+
buildscript {
27+
ext.kotlin_version = '1.3.30-eap-125'
28+
repositories {
29+
maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
30+
mavenCentral()
31+
}
32+
dependencies {
33+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
34+
}
35+
}
36+
repositories {
37+
maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
38+
mavenCentral()
39+
}
40+
compileKotlin {
41+
kotlinOptions {
42+
jvmTarget = "1.8"
43+
}
44+
}
45+
compileTestKotlin {
46+
kotlinOptions {
47+
jvmTarget = "1.8"
48+
}
49+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
Created by Eric Mikulin, 2015
3+
4+
This program is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
This program is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
/*
18+
Image to ASCII Art Converter
19+
Designed for a SWCHS CompSci Club fortnight challenge
20+
Challenge Components: Speed, Input variety and Output Aesthetics
21+
*/
22+
23+
package com.willowtreeapps.namegame.jvmcli;
24+
25+
//Import the Packages
26+
27+
import java.awt.image.BufferedImage;
28+
import java.io.IOException;
29+
import java.net.URL;
30+
import java.util.HashMap;
31+
import java.util.Map;
32+
33+
import javax.imageio.ImageIO;
34+
35+
public class ImageConverter implements Runnable {
36+
//Create a map to map between darknesses (int) and characters (ASCII based on darkness). See mapit(); for detail
37+
static Map<Integer, Character> asciiMap = new HashMap<Integer, Character>();
38+
//Create the image and link
39+
static BufferedImage image;
40+
static URL link;
41+
42+
public static void displayImage(String url) throws IOException {
43+
//Create the thread for loading the image
44+
ImageConverter load = new ImageConverter(); //create another instance of this class
45+
Thread loadThread = new Thread(load); //Turn that into a thread
46+
47+
//Map characters to integers
48+
mapit();
49+
50+
link = new URL(url);
51+
loadThread.start(); //Start the thread
52+
53+
//Ask for the "Line Skip Coefficient"
54+
//Basically, this is the amount the x and y values of the coordinate increase each time. Larger = smaller image.
55+
//Anything above 2 usually doesn't work too well
56+
int skipC = 2;
57+
58+
59+
//Test if the thread is still running, then wait for it to finish if it is
60+
if (loadThread.isAlive()) {
61+
try {
62+
loadThread.join(); //Wait for thread
63+
} catch (InterruptedException e) {
64+
e.printStackTrace();
65+
}
66+
}
67+
68+
//Get the length and width of the image, storx`e as integer values
69+
int imgWidth = image.getWidth();
70+
int imgHeight = image.getHeight();
71+
72+
73+
//This is the conversion loop, goes through every x for each y line
74+
for (int y = 0; y < imgHeight; y += 2 * skipC) {
75+
for (int x = 0; x < imgWidth; x += skipC) {
76+
System.out.print(convert(image.getRGB(x, y)));
77+
}
78+
System.out.println();
79+
}
80+
}
81+
82+
//Converts the RGB int value to a char, based on the amount of color in the pixel
83+
private static char convert(int value) {
84+
//Grab the three values for each red, green and blue (and alpha)
85+
int alpha = (value >> 24) & 0xFF;
86+
int red = (value >> 16) & 0xFF;
87+
int green = (value >> 8) & 0xFF;
88+
int blue = (value) & 0xFF;
89+
90+
//Covert to a unified integer value between 0 and 26.
91+
//This is done by averaging, then dividing by 10 (RGB values range from 0 to 255)
92+
int darkness = ((int) ((0.21 * red) + (0.72 * green) + (0.07 * blue) / 3) / 10);
93+
94+
//If alpha is completely clear, assume white
95+
if (alpha == 0)
96+
darkness = 26;
97+
98+
//Output the respective char, grabbing it from the hashmap
99+
char chart = asciiMap.get(darkness);
100+
return chart;
101+
}
102+
103+
//This function creates the actual link from the integers and chars
104+
private static void mapit() {
105+
//Map an integer darkness value to an ASCII character
106+
//The value of darkness for the chars I determined from some other random Internet source
107+
asciiMap.put(0, '@');
108+
asciiMap.put(1, '@');
109+
asciiMap.put(2, '@');
110+
asciiMap.put(3, '@');
111+
asciiMap.put(4, '@');
112+
113+
asciiMap.put(5, '@');
114+
asciiMap.put(6, 'N');
115+
asciiMap.put(7, '%');
116+
asciiMap.put(8, 'W');
117+
asciiMap.put(9, '$');
118+
119+
asciiMap.put(10, 'D');
120+
asciiMap.put(11, 'd');
121+
asciiMap.put(12, 'x');
122+
asciiMap.put(13, '6');
123+
asciiMap.put(14, 'E');
124+
125+
asciiMap.put(15, '5');
126+
asciiMap.put(16, '{');
127+
asciiMap.put(17, 's');
128+
asciiMap.put(18, '?');
129+
asciiMap.put(19, '!');
130+
131+
asciiMap.put(20, ';');
132+
asciiMap.put(21, '"');
133+
asciiMap.put(22, ':');
134+
asciiMap.put(23, '_');
135+
asciiMap.put(24, '\'');
136+
137+
asciiMap.put(25, '`');
138+
asciiMap.put(26, ' ');
139+
}
140+
141+
//The function run for when you need multi-thread
142+
public void run() {
143+
try {
144+
image = ImageIO.read(link); //Load the image from the web
145+
} catch (IOException e) {
146+
e.printStackTrace();
147+
}
148+
}
149+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
@file:JvmName("Main")
2+
3+
package com.willowtreeapps.namegame.jvmcli
4+
5+
import com.willowtreeapps.common.GameEngine
6+
import com.willowtreeapps.common.GameResultsViewState
7+
import com.willowtreeapps.common.QuestionViewState
8+
import com.willowtreeapps.common.middleware.Navigator
9+
import com.willowtreeapps.common.middleware.Screen
10+
import com.willowtreeapps.common.ui.*
11+
import kotlinx.coroutines.Dispatchers
12+
import java.util.*
13+
14+
/**
15+
* Proof of concept CLI implementation of NameGame using ascii art for images. Images are hard to recognize
16+
* without tweaking CLI font and size. Not fully implemented or functioning. Shows first image and
17+
* does not respond to input.
18+
* Run with ./gradlew jvmcli:runMain
19+
*/
20+
21+
val gameEngine = GameEngine(CliNavigator(), Any(), Dispatchers.Unconfined, Dispatchers.Unconfined)
22+
val scanner = Scanner(System.`in`)
23+
var view: CliView = Start
24+
25+
fun main() {
26+
print("Hello World")
27+
28+
Start.onCreate()
29+
while (true) {
30+
view.handleInput(scanner.next())
31+
32+
}
33+
}
34+
35+
interface CliView {
36+
fun onCreate()
37+
fun onDestroy()
38+
fun handleInput(input: String)
39+
}
40+
41+
object Start : StartView, CliView {
42+
private var presenter: StartPresenter? = null
43+
44+
override fun handleInput(input: String) {
45+
when (input) {
46+
"yes", "y" -> presenter?.startGame()
47+
}
48+
}
49+
50+
override fun onCreate() {
51+
presenter = gameEngine.attachView(this) as StartPresenter
52+
presenter?.startGame()
53+
print("Ready to play?")
54+
}
55+
56+
override fun onDestroy() {
57+
gameEngine.detachView(this)
58+
}
59+
60+
override fun showLoading() {
61+
print("Loading...")
62+
}
63+
64+
override fun hideLoading() {
65+
print("----")
66+
}
67+
68+
override fun showError(msg: String) {
69+
print("Error: $msg")
70+
}
71+
72+
}
73+
74+
object Question: QuestionView, CliView {
75+
private var presenter: QuestionPresenter? = null
76+
77+
override fun onCreate() {
78+
presenter = gameEngine.attachView(this) as QuestionPresenter
79+
}
80+
81+
override fun onDestroy() {
82+
gameEngine.detachView(this)
83+
}
84+
85+
override fun handleInput(input: String) {
86+
presenter?.namePicked(input)
87+
}
88+
89+
override fun showProfile(viewState: QuestionViewState) {
90+
ImageConverter.displayImage(viewState.profileImageUrl)
91+
}
92+
93+
override fun showCorrectAnswer(viewState: QuestionViewState, isEndGame: Boolean) {
94+
print("Correct!!")
95+
}
96+
97+
override fun showWrongAnswer(viewState: QuestionViewState, isEndGame: Boolean) {
98+
print("Wrong!!")
99+
}
100+
101+
override fun setTimerText(viewState: QuestionViewState) {
102+
print(viewState.timerText)
103+
}
104+
105+
override fun showTimesUp(viewState: QuestionViewState, isEndGame: Boolean) {
106+
print(viewState.timerText)
107+
}
108+
109+
110+
111+
}
112+
113+
object GameResults: GameResultsView, CliView {
114+
override fun onCreate() {
115+
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
116+
}
117+
118+
override fun onDestroy() {
119+
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
120+
}
121+
122+
override fun showResults(viewState: GameResultsViewState) {
123+
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
124+
}
125+
126+
override fun handleInput(input: String) {
127+
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
128+
}
129+
130+
}
131+
132+
class CliNavigator : Navigator {
133+
override fun goto(screen: Screen) {
134+
view.onDestroy()
135+
view = when (screen) {
136+
Screen.START -> Start
137+
Screen.QUESTION -> Question
138+
Screen.GAME_COMPLETE -> GameResults
139+
else -> throw IllegalArgumentException()
140+
}
141+
view.onCreate()
142+
}
143+
}

settings.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
include ':app', ':common'
1+
include ':app', ':common', ':jvmcli'
22
enableFeaturePreview("GRADLE_METADATA")

0 commit comments

Comments
 (0)