uni

University stuff
git clone git://git.christosmarg.xyz/uni-assignments.git
Log | Files | Refs | README | LICENSE

commit 309d7ea994ccc1197093dcb4da1eafccf1af4ac1
parent b880ec667e1d2052ed458ee6db0efd6f6b47119b
Author: Christos Margiolis <christos@margiolis.net>
Date:   Sat, 12 Jun 2021 00:23:07 +0300

potter done, i'm on fire today

Diffstat:
Mcpp_oop/game/Engine.cc | 333++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mcpp_oop/game/Engine.hpp | 19++++++++++++-------
Mcpp_oop/game/Makefile | 12+++---------
Mcpp_oop/game/Potter.cc | 3+--
Mcpp_oop/game/Score.cc | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mcpp_oop/game/Score.hpp | 26+++++++++++++++++---------
Mcpp_oop/game/main.cc | 16+++++++++++++++-
Mcpp_oop/game/res/score | 0
Mjava_development/population/.classpath | 4++--
Djava_development/population/bin/population/Chart.class | 0
Djava_development/population/bin/population/Column.class | 0
Djava_development/population/bin/population/Country.class | 0
Djava_development/population/bin/population/ExcelParser.class | 0
Djava_development/population/bin/population/Main.class | 0
Djava_development/population/bin/population/MainWindow.class | 0
Djava_development/population/bin/population/Toast.class | 0
Mjava_development/population/src/population/Chart.java | 88+++++++++++++++++++++++++++----------------------------------------------------
Mjava_development/population/src/population/ExcelParser.java | 10++--------
Mjava_development/population/src/population/Main.java | 11++++++++---
Mjava_development/population/src/population/MainWindow.java | 13++++++++-----
20 files changed, 404 insertions(+), 198 deletions(-)

diff --git a/cpp_oop/game/Engine.cc b/cpp_oop/game/Engine.cc @@ -11,61 +11,67 @@ enum Color { LAST }; -/* TODO get player name */ -Engine::Engine(const char *mapfile, const char *scorefile) +Engine::Engine(const char *mapfile, const char *scorefile, const char *name) { + /* + * Initialize curses(3) first so we can get the terminal's dimensions + * and use them in `load_map`. + */ if (!init_curses()) throw std::runtime_error("init_curses failed"); + /* - * We'll use exceptions here because we want to display a useful - * error message since `load_map` and `Score`'s constructor - * have many points of failure. If we do catch an exception, - * we'll just "forward" it to `main`. + * We'll use std::runtime_error exceptions here because we + * want to display a useful error message since `load_map` + * and `Score`'s constructor have many points of failure. + * If we do catch an exception, we'll just "forward" it + * to `main`. */ try { load_map(mapfile); - score = new Score(scorefile); + score = new Score(scorefile, name); } catch (const std::ios_base::failure& e) { + /* + * Kill the curses window, otherwise the terminal output + * will be messed up. + */ + (void)endwin(); throw std::runtime_error("error: " + std::string(e.what())); } catch (const std::runtime_error& e) { + (void)endwin(); throw std::runtime_error("error: " + std::string(e.what())); } - - if (!init_gamewin()) + if (!init_gamewin()) { + (void)endwin(); throw std::runtime_error("init_gamewin failed"); - init_entities(); - prch = nullptr; - - /* Initialize messages for the popup windows. */ - ctrls = { - "Up Move up", - "Down Move down", - "Left Move left", - "Right Move right", - "ESC Quit", - "s High Scores", - "c Controls menu", - "Press any key to quit the menu", - }; - rules = { - " Babis Potter", - "--------------------------------------------------------", - "The objective is to collect all the gems without getting", - "caught by the Gnomes and Traals!", - "", - "You can always see what controls are available by", - "pressing the 'c' key.", - " Press any key to continue", - }; + } + reset_entities(); + init_popup_msgs(); /* Display a welcome message. */ - popup(rules); + popup(p_rules); f_running = 1; } - Engine::~Engine() { + delete score; + free_entities(); + map.clear(); + p_ctrls.clear(); + p_rules.clear(); + p_win.clear(); + p_lose.clear(); + (void)delwin(gw); + (void)endwin(); +} + +/* + * Clean up all moving entities and gems. + */ +void +Engine::free_entities() +{ player = nullptr; for (auto&& e : entities) delete e; @@ -73,16 +79,17 @@ Engine::~Engine() delete g; if (prch != nullptr) delete prch; - delete score; - map.clear(); - colors.clear(); - ctrls.clear(); - rules.clear(); - (void)delwin(gw); - (void)endwin(); + entities.clear(); + gems.clear(); } -/* Private methods */ +void +Engine::reset_entities() +{ + free_entities(); + init_entities(); + prch = nullptr; +} /* * Initialize curses(3) environment @@ -90,28 +97,45 @@ Engine::~Engine() bool Engine::init_curses() { + std::vector<int> colors; + if (!initscr()) return false; + /* Don't echo keypresses. */ noecho(); + /* Disable line buffering. */ cbreak(); + /* Hide the cursor. */ curs_set(false); - /* Allow arrow key presses. */ + /* Allow arrow-key usage. */ keypad(stdscr, true); - /* ESC has a small delay after it's pressed, so remove it. */ + /* ESC has a small delay after it's pressed, so we'll remove it. */ set_escdelay(0); - /* Don't wait for a keypress, just continue if there's nothing. */ + /* + * Don't wait for a keypress -- just continue if there's no keypress + * within 1000 milliseconds. We could set the delay to 0 milliseconds, + * but this will most likely burn the CPU. + */ timeout(1000); (void)getmaxyx(stdscr, ymax, xmax); - colors.push_back(COLOR_BLUE); /* Wall */ - colors.push_back(COLOR_RED); /* Path */ - colors.push_back(COLOR_CYAN); /* Potter */ - colors.push_back(COLOR_GREEN); /* Gnome */ - colors.push_back(COLOR_YELLOW); /* Traal */ - colors.push_back(COLOR_WHITE); /* Stone */ - colors.push_back(COLOR_BLACK); /* Parchment */ + /* + * This has to follow the same order as the enum declaration at + * the top of the file. Sadly, we cannot use C99's designated + * initialization. + */ + colors = { + COLOR_BLUE, /* Wall */ + COLOR_RED, /* Path */ + COLOR_CYAN, /* Potter */ + COLOR_GREEN, /* Gnome */ + COLOR_YELLOW, /* Traal */ + COLOR_WHITE, /* Stone */ + COLOR_BLACK, /* Parchment */ + }; start_color(); + /* Use the terminal's colorscheme. */ use_default_colors(); for (int i = 1; i < Color::LAST; i++) (void)init_pair(i, colors[i-1], -1); @@ -119,6 +143,10 @@ Engine::init_curses() return true; } +/* + * Initiliaze the game window. Having a seperate window for the game + * will make it easier to handle the map and player input. + */ bool Engine::init_gamewin() { @@ -186,7 +214,7 @@ Engine::load_map(const char *mapfile) f.close(); /* * Since we got here, we know that number of columns is the same for - * every row, so we can now just take a random string and calculate its + * every row -- we can now just take a random string and calculate its * size in order to get the map's width. */ w = map[0].length(); @@ -209,6 +237,9 @@ Engine::collides_with_wall(int x, int y) const return true; } +/* + * Calculate a new position for an entity or gem. + */ void Engine::calc_pos(int *x, int *y) { @@ -216,8 +247,8 @@ Engine::calc_pos(int *x, int *y) *x = rand() % w; *y = rand() % h; /* - * Don't spawn an enemy at the same coordinates with - * another entity. + * Don't spawn at the same coordinates with another entity + * or gem. */ for (const auto& e : entities) if (*x == e->get_x() && *y == e->get_y()) @@ -257,6 +288,8 @@ Engine::init_entities() calc_pos(&x, &y); gems.push_back(new Gem(x, y, SYM_STONE)); } + + /* Potter is *always* the first entry in `entities`. */ player = (Potter *)entities[0]; } @@ -272,7 +305,7 @@ Engine::spawn_parchment() /* * Draw a popup window with the `lines` argument as contents. */ -void +int Engine::popup(const std::vector<std::string>& lines) const { WINDOW *win; @@ -281,6 +314,7 @@ Engine::popup(const std::vector<std::string>& lines) const }; std::size_t vecsz; int wr, wc, wy, wx; + int ch; vecsz = lines.size(); /* @@ -296,7 +330,7 @@ Engine::popup(const std::vector<std::string>& lines) const wx = CENTER(xmax, wc); wy = CENTER(ymax, wr); if ((win = newwin(wr, wc, wy, wx)) == NULL) - return; + return ERR; werase(win); box(win, 0, 0); for (std::size_t i = 0; i < vecsz; i++) { @@ -310,35 +344,25 @@ Engine::popup(const std::vector<std::string>& lines) const mvwprintw(win, i + 2, 1, lines[i].c_str()); } wrefresh(win); - (void)wgetch(win); + + /* Save the key we pressed -- it's useful in `round_end`. */ + ch = wgetch(win); werase(win); wrefresh(win); (void)delwin(win); -} -void -Engine::calc_dist(std::map<int, int>& dists, int ex, int ey, int dir) const -{ - int px, py, dx, dy, d; - - px = player->get_x(); - py = player->get_y(); - dx = ex - px; - dy = ey - py; - d = floor(sqrt(dx * dx + dy * dy)); - dists.insert(std::pair<int, int>(d, dir)); + return ch; } -/* Public methods */ - void Engine::kbd_input() { int key, dir, newx, newy; - dir = 0; newx = player->get_x(); newy = player->get_y(); + /* g++ was complaining `dir` wasn't initialized. */ + dir = 0; switch (key = getch()) { case KEY_LEFT: @@ -358,15 +382,23 @@ Engine::kbd_input() dir = Movable::Direction::DOWN; break; case 'c': - popup(ctrls); + (void)popup(p_ctrls); return; case 's': - popup(score->scores_strfmt()); + (void)popup(score->scores_strfmt()); + break; + case 'r': + /* Reset the score as well. */ + upd_score(-score->get_curscore()); + reset_entities(); break; case ESC: /* FALLTHROUGH */ - /* FIXME: what? */ f_running = 0; default: + /* + * If no key was pressed, just return -- we + * don't want to move the player. + */ return; } @@ -374,7 +406,38 @@ Engine::kbd_input() player->set_newpos(dir, wxmax, wymax); } -/* FIXME: move asynchronously */ +/* + * Calculate the Eucledean 2D distance from an enemy to the player. + * + * Each new distance calculated is added to the `dists` map, which + * contains the distance and also a direction associated with it. + */ +void +Engine::calc_dist(std::map<int, int>& dists, int ex, int ey, int dir) const +{ + int px, py, dx, dy, d; + + px = player->get_x(); + py = player->get_y(); + dx = ex - px; + dy = ey - py; + d = floor(sqrt(dx * dx + dy * dy)); + dists.insert(std::pair<int, int>(d, dir)); +} + +/* + * Definitely not a sophisticated pathfinding algorithm, but the way it + * works is this: + * + * 1. For each possible direction (west, east, north, south), see if + * a movement is possible in the first place -- i.e we won't hit a + * wall. + * 2. Calculate the Eucledean distance for each possible direction + * and save it in a map (distance, direction). + * 3. Sort the map, and get the minimum distance, this is the shortest + * path to the player. + * 4. Get the direction from the minimum pair in the map and go there. + */ void Engine::enemies_move() { @@ -389,6 +452,7 @@ Engine::enemies_move() continue; ex = e->get_x(); ey = e->get_y(); + /* Clear previous entity's data. */ dists.clear(); /* West */ if (!collides_with_wall(ex - 1, ey)) @@ -403,6 +467,10 @@ Engine::enemies_move() if (!collides_with_wall(ex, ey + 1)) calc_dist(dists, ex, ey + 1, Movable::Direction::DOWN); + /* + * If `dists` is not empty, it means we have found at + * least one valid movement. + */ if (!dists.empty()) { auto min = std::min_element(dists.begin(), dists.end(), distcmp); @@ -411,7 +479,9 @@ Engine::enemies_move() } } -/* TODO: define constants for scores */ +/* + * See if the player collides with either an enemy, gem or the parchment. + */ void Engine::collisions() { @@ -420,40 +490,91 @@ Engine::collisions() px = player->get_x(); py = player->get_y(); + /* Collision with an enemy. */ for (const auto& e : entities) { x = e->get_x(); y = e->get_y(); - if (e != player && px == x && py == y) { - /* TODO: Handle this. */ - } + if (e != player && px == x && py == y) + round_end(false); } + + /* Collision with a gem. */ for (auto& g : gems) { x = g->get_x(); y = g->get_y(); if (px == x && py == y) { - upd_score(10); + upd_score(SCORE_STONE); delete g; + /* If we hit a gem, remove it from the vector. */ gems.erase(std::remove(gems.begin(), gems.end(), g), gems.end()); } } + + /* + * The parchment has been spawned, if we collide with + * it, we won the round. + */ if (gems.empty() && prch != nullptr) { x = prch->get_x(); y = prch->get_y(); if (px == x && py == y) { - upd_score(100); - /* TODO: YOU WON + delete + reset */ + upd_score(SCORE_PARCHMENT); + delete prch; + round_end(true); } + /* + * If the `gems` vector is empty, we need to spawn the + * parchment. + */ } else if (gems.empty() && prch == nullptr) spawn_parchment(); } +/* + * Update the score after each round. + */ void Engine::upd_score(int n) { *score << score->get_curname() << score->get_curscore() + n; } +/* + * Let the user choose if he wants to start a new round or exit the game. + */ +void +Engine::round_end(bool is_win) +{ + int ch; + + /* + * If we lost, reset the score in case the user starts a new + * round. We keep the score only if the player wins the round. + */ + if (!is_win) + upd_score(-score->get_curscore()); + /* If we won, increase the number of enemies. */ + else + nenemies++; + + /* + * Get out of here only if the user presses 'n' or 'q' + * because it's very easy to accidentally mislick and + * exit the game. + */ + for (;;) { + switch (ch = popup(is_win ? p_win : p_lose)) { + case 'n': + reset_entities(); + return; + case 'q': + f_running = 0; + return; + } + } +} + void Engine::redraw() const { @@ -471,7 +592,7 @@ Engine::redraw() const mvprintw(0, xmax - strlen(msg_opts), msg_opts); mvhline(1, 0, ACS_HLINE, xmax); - /* Draw everything */ + /* Draw everything. */ wattron(gw, A_REVERSE); draw_map(); draw_entities(); @@ -488,6 +609,11 @@ Engine::draw_map() const { int color; + /* + * Even though the map is stored as a `std::vector<std::string>`, + * we'll loop through it character by character so we can set + * the colors. + */ for (const auto& row : map) { for (const auto& c : row) { if (c == SYM_WALL) @@ -549,3 +675,38 @@ Engine::is_running() const { return f_running; } + +/* Initialize messages for the popup windows. */ +void +Engine::init_popup_msgs() +{ + p_ctrls = { + "Up Move up", + "Down Move down", + "Left Move left", + "Right Move right", + "ESC Quit", + "s High Scores", + "c Controls menu", + "r Restart game", + "Press any key to quit the menu", + }; + p_rules = { + " Babis Potter", + "--------------------------------------------------------", + "The objective is to collect all the gems without getting", + "caught by the Gnomes and Traals!", + "", + "You can always see what controls are available by", + "pressing the 'c' key.", + " Press any key to continue", + }; + p_win = { + " You won!", + "Press 'n' to play a new round or 'q' to quit.", + }; + p_lose = { + " You lost!", + "Press 'n' to play a new round or 'q' to quit.", + }; +} diff --git a/cpp_oop/game/Engine.hpp b/cpp_oop/game/Engine.hpp @@ -3,7 +3,6 @@ #include <algorithm> #include <cmath> -#include <csignal> #include <cstdlib> #include <cstring> #include <ctime> @@ -42,9 +41,10 @@ private: std::vector<Movable *> entities; std::vector<Gem *> gems; std::vector<std::string> map; - std::vector<int> colors; - std::vector<std::string> ctrls; - std::vector<std::string> rules; + std::vector<std::string> p_ctrls; + std::vector<std::string> p_rules; + std::vector<std::string> p_win; + std::vector<std::string> p_lose; Potter *player; Score *score; Gem *prch; @@ -57,10 +57,11 @@ private: int h; int nenemies = 2; int ngems = 10; - volatile sig_atomic_t f_running; + int f_running; public: - explicit Engine(const char *mapfile, const char *scorefile); + explicit Engine(const char *mapfile, const char *scorefile, + const char *name); ~Engine(); void kbd_input(); @@ -70,16 +71,20 @@ public: bool is_running() const; private: + void free_entities(); + void reset_entities(); bool init_curses(); bool init_gamewin(); void load_map(const char *mapfile); void calc_pos(int *x, int *y); void init_entities(); + void init_popup_msgs(); void spawn_parchment(); bool collides_with_wall(int x, int y) const; - void popup(const std::vector<std::string>& lines) const; + int popup(const std::vector<std::string>& lines) const; void calc_dist(std::map<int, int>& dists, int ex, int ey, int dir) const; void upd_score(int n); + void round_end(bool is_win); void draw_map() const; void draw_entities() const; void draw_gems() const; diff --git a/cpp_oop/game/Makefile b/cpp_oop/game/Makefile @@ -1,11 +1,8 @@ -# compiler -CC = c++ +.POSIX: -# includes and libs +CC = c++ INCS = -Iinclude LIBS = -Llib -lncursesw -lm - -# flags CFLAGS = -std=c++14 -pedantic -Wall -Os ${INCS} LDFLAGS = ${LIBS} @@ -36,10 +33,7 @@ ${BIN}: ${OBJ} .cc.o: ${CC} -c ${CFLAGS} $< -run: - ./${BIN} - clean: rm -f ${BIN} ${OBJ} *.core -.PHONY: all options clean run +.PHONY: all options clean diff --git a/cpp_oop/game/Potter.cc b/cpp_oop/game/Potter.cc @@ -3,4 +3,4 @@ Potter::Potter(int x, int y, int dir, char sym) :Movable(x, y, dir, sym) { -}- \ No newline at end of file +} diff --git a/cpp_oop/game/Score.cc b/cpp_oop/game/Score.cc @@ -1,28 +1,34 @@ #include "Score.hpp" -Score::Score(const char *scorefile) +Score::Score(const char *scorefile, const char *name) { fpath = std::string(scorefile); - sf.exceptions(std::fstream::badbit | std::fstream::failbit); + + sf.exceptions(std::fstream::badbit); sf.open(fpath, std::fstream::in | std::fstream::binary); if (!sf.is_open()) throw std::runtime_error(fpath + ": cannot open file"); - for (int i = 0; i < SCORES_LEN; i++) { + for (int i = 0; i < hiscores.size(); i++) { sf.read((char *)&hiscores[i].name, sizeof(hiscores[i].name)); sf.read((char *)&hiscores[i].score, sizeof(hiscores[i].score)); } sf.close(); - /* FIXME: how to get player name? */ - (void)strncpy(curname, "christos", sizeof(curname)); + /* Initialize the current name and score. */ + (void)strncpy(curname, name, sizeof(curname)); curscore = 0; } +/* + * I'm not sure if this stupid or not... + */ Score::~Score() { + add_new_hiscore(); + sf.open(fpath, std::fstream::out | std::fstream::binary); if (sf.is_open()) { - for (int i = 0; i < SCORES_LEN; i++) { + for (int i = 0; i < hiscores.size(); i++) { sf.write((char *)&hiscores[i].name, sizeof(hiscores[i].name)); sf.write((char *)&hiscores[i].score, @@ -32,8 +38,28 @@ Score::~Score() } } +/* + * The following 2 overloads return a reference to `this`, so that + * we can chain arguments together. + * + * Take the following chain as an example: + * `*score << "string" << 100` + * + * First, `*score << "string"` will call the overload which + * takes a `const char *` , and will return a reference to `this`. + * + * After the first overload has returned, `*score << "string"` will + * be replaced with just `*score` and the initial overload will + * look like this: + * `*score << 100` + * + * Now, the overload getting a `const int` argument will get called. + * + * Technically, this chain can work no matter the order or number + * of the arguments. + */ Score& -Score::operator<< (const char name[NAME_LEN]) +Score::operator<< (const char *name) { (void)strncpy(curname, name, sizeof(curname)); return *this; @@ -55,7 +81,7 @@ Score::scores_strfmt() const { std::vector<std::string> v; - for (int i = 0; i < SCORES_LEN; i++) + for (int i = 0; i < hiscores.size(); i++) v.push_back(std::string(hiscores[i].name) + ": " + std::to_string(hiscores[i].score)); v.push_back("Press any key to continue"); @@ -74,3 +100,28 @@ Score::get_curscore() const { return curscore; } + +void +Score::add_new_hiscore() +{ + auto cmp = [](const HighScore& a, const HighScore& b) -> bool { + return a.score > b.score; + }; + + /* + * Add our new score in the array in case it's higher + * than any of the existing entries. The array is sorted + * in descending order, so we'll search it backwards -- + * this will have the effect of replacing the lower scores + * first. + */ + for (int i = hiscores.size() - 1; i >= 0; i--) { + if (curscore > hiscores[i].score) { + hiscores[i].score = curscore; + (void)strncpy(hiscores[i].name, curname, + sizeof(hiscores[i].name)); + break; + } + } + std::sort(hiscores.begin(), hiscores.end(), cmp); +} diff --git a/cpp_oop/game/Score.hpp b/cpp_oop/game/Score.hpp @@ -1,36 +1,44 @@ #ifndef _SCORE_HPP_ #define _SCORE_HPP_ +#include <algorithm> +#include <array> #include <cstring> #include <fstream> #include <stdexcept> #include <string> #include <vector> +#define NAMEMAX 11 +#define SCORE_STONE 10 +#define SCORE_PARCHMENT 100 + class Score { private: - static const int NAME_LEN = 10; - static const int SCORES_LEN = 5; - struct HighScore { - char name[NAME_LEN]; + char name[NAMEMAX]; int score; - } hiscores[SCORES_LEN]; - + }; + + std::array<HighScore, 5> hiscores; std::string fpath; std::fstream sf; - char curname[NAME_LEN]; + char curname[NAMEMAX]; int curscore; public: - Score(const char *scorefile); + Score(const char *scorefile, const char *name); ~Score(); - Score& operator<< (const char name[NAME_LEN]); + Score& operator<< (const char *name); Score& operator<< (const int score); + std::vector<std::string> scores_strfmt() const; const char *get_curname() const; int get_curscore() const; + +private: + void add_new_hiscore(); }; #endif /* _SCORE_HPP_ */ diff --git a/cpp_oop/game/main.cc b/cpp_oop/game/main.cc @@ -22,6 +22,7 @@ main(int argc, char *argv[]) { Engine *eng; char *mapfile, *scorefile; + char name[NAMEMAX]; argv0 = *argv++; if (argc < 3) @@ -39,8 +40,21 @@ main(int argc, char *argv[]) if (!setlocale(LC_ALL, "")) die("setlocale"); + do { + std::cout << "\rPlayer name: "; + std::cin >> name; + /* Make sure we read valid input. */ + } while (strlen(name) >= NAMEMAX || std::cin.fail()); + + /* + * We'll guarantee the name is null-terminated and has a + * length < NAMEMAX so we don't have to do any checks in + * the other classes. + */ + name[strlen(name)] = '\0'; + try { - eng = new Engine(mapfile, scorefile); + eng = new Engine(mapfile, scorefile, name); } catch (const std::runtime_error& e) { die(e.what()); } diff --git a/cpp_oop/game/res/score b/cpp_oop/game/res/score Binary files differ. diff --git a/java_development/population/.classpath b/java_development/population/.classpath @@ -1,12 +1,12 @@ <?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry kind="src" path="src"/> - <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/JavaFX"/> - <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/POI"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"> <attributes> <attribute name="module" value="true"/> </attributes> </classpathentry> + <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/JavaFX"/> + <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/POI"/> <classpathentry kind="output" path="bin/"/> </classpath> diff --git a/java_development/population/bin/population/Chart.class b/java_development/population/bin/population/Chart.class Binary files differ. diff --git a/java_development/population/bin/population/Column.class b/java_development/population/bin/population/Column.class Binary files differ. diff --git a/java_development/population/bin/population/Country.class b/java_development/population/bin/population/Country.class Binary files differ. diff --git a/java_development/population/bin/population/ExcelParser.class b/java_development/population/bin/population/ExcelParser.class Binary files differ. diff --git a/java_development/population/bin/population/Main.class b/java_development/population/bin/population/Main.class Binary files differ. diff --git a/java_development/population/bin/population/MainWindow.class b/java_development/population/bin/population/MainWindow.class Binary files differ. diff --git a/java_development/population/bin/population/Toast.class b/java_development/population/bin/population/Toast.class Binary files differ. diff --git a/java_development/population/src/population/Chart.java b/java_development/population/src/population/Chart.java @@ -22,6 +22,7 @@ import javafx.stage.StageStyle; public class Chart { private final int WIDTH = 1280; private final int HEIGHT = 1000; + private final String TITLE = "Population Chart"; private Stage stg; private VBox vb; private HBox hb; @@ -34,18 +35,16 @@ public class Chart { private NumberAxis yaxis; private LineChart<Number, Number> lc; private ArrayList<XYChart.Series<Number, Number>> sr; - private List<Country> countries; Chart(List<Country> countries) { - this.countries = countries; - stg = mkstage(); - xaxis = mkxaxis("Year"); - yaxis = mkyaxis("Population"); sr = new ArrayList<XYChart.Series<Number, Number>>(); - lc = mklinechart(xaxis, yaxis, stg); - btn_close = mkclosebtn(stg, "Close"); - btn_clear = mkclearbtn(stg, sr, lc, "Clear"); - cb_ctry = mkctrylist(sr, lc, "Country"); + mkstage(); + mkxaxis("Year"); + mkyaxis("Population"); + mklinechart(); + mkclosebtn("Close"); + mkclearbtn("Clear"); + mkctrylist(countries, "Country"); cb_yearfrom = mkyearcb(Country.STARTING_YEAR); cb_yearto = mkyearcb(Country.LAST_YEAR); @@ -60,19 +59,16 @@ public class Chart { }); } - private Stage mkstage() { - Stage stg = new Stage(); - + private void mkstage() { + stg = new Stage(); stg.setWidth(WIDTH); stg.setHeight(HEIGHT); stg.initStyle(StageStyle.UTILITY); stg.initModality(Modality.WINDOW_MODAL); - return stg; } - private NumberAxis mkxaxis(String str) { - NumberAxis xaxis = new NumberAxis(); - + private void mkxaxis(String str) { + xaxis = new NumberAxis(); xaxis.setLabel(str); /* * By default, the chart isn't resized properly @@ -82,30 +78,14 @@ public class Chart { xaxis.setUpperBound(Country.LAST_YEAR); /* No. Stop auto resizing. */ xaxis.setAutoRanging(false); - return xaxis; } - private NumberAxis mkyaxis(String str) { - NumberAxis yaxis = new NumberAxis(); - + private void mkyaxis(String str) { + yaxis = new NumberAxis(); yaxis.setLabel(str); - return yaxis; } - private XYChart.Series<Number, Number> mkseries(String str) { - XYChart.Series<Number, Number> sr; - - sr = new XYChart.Series<Number, Number>(); - sr.setName(str); - return sr; - } - - private LineChart<Number, Number> mklinechart( - NumberAxis xaxis, - NumberAxis yaxis, - Stage stg) { - LineChart<Number, Number> lc; - + private void mklinechart() { lc = new LineChart<Number, Number>(xaxis, yaxis); /* Fit to screen. */ @@ -120,37 +100,26 @@ public class Chart { /* Do not draw nodes, just the line. */ lc.setCreateSymbols(false); - - return lc; } - private Button mkclosebtn(Stage stg, String str) { - Button btn_close = new Button(str); - + private void mkclosebtn(String str) { + btn_close = new Button(str); btn_close.setOnAction(ev -> { stg.close(); }); - return btn_close; } - private Button mkclearbtn( - Stage stg, - ArrayList<XYChart.Series<Number, Number>> sr, - LineChart<Number, Number> lc, - String str) { - Button btn_clear = new Button(str); - + private void mkclearbtn(String str) { + btn_clear = new Button(str); btn_clear.setOnAction(ev -> { lc.getData().clear(); sr.forEach(s -> s.getData().clear()); sr.clear(); }); - return btn_clear; } private ComboBox<Integer> mkyearcb(Integer n) { ComboBox<Integer> cb = new ComboBox<Integer>(); - for (int i = Country.STARTING_YEAR; i <= Country.LAST_YEAR; i++) cb.getItems().add(i); /* @@ -162,12 +131,8 @@ public class Chart { return cb; } - private ComboBox<String> mkctrylist( - ArrayList<XYChart.Series<Number, Number>> sr, - LineChart<Number, Number> lc, - String str) { - ComboBox<String> cb_ctry = new ComboBox<String>(); - + private void mkctrylist(List<Country> countries, String str) { + cb_ctry = new ComboBox<String>(); countries.forEach(c -> cb_ctry.getItems().add(c.getName())); cb_ctry.setPromptText(str); /* @@ -209,7 +174,14 @@ public class Chart { } lc.getData().add(sr.get(sr.size()-1)); }); - return cb_ctry; + } + + private XYChart.Series<Number, Number> mkseries(String str) { + XYChart.Series<Number, Number> series; + + series = new XYChart.Series<Number, Number>(); + series.setName(str); + return series; } public void show() { @@ -227,7 +199,7 @@ public class Chart { vb.setSpacing(5); vb.setPadding(new Insets(10, 10, 10, 10)); vb.getChildren().addAll(lc, hb); - stg.setTitle("Population Chart"); + stg.setTitle(TITLE); stg.setScene(new Scene(vb, 400, 200)); stg.show(); } diff --git a/java_development/population/src/population/ExcelParser.java b/java_development/population/src/population/ExcelParser.java @@ -15,11 +15,11 @@ public class ExcelParser { private final int TARGET_TYPE_CELL = 5; private final int COUNTRIES_ROW = 43; private List<Country> countries; - private XSSFRow row; ExcelParser(String path) throws Exception { XSSFWorkbook wbook; XSSFSheet sheet; + XSSFRow row; FileInputStream fis; Iterator<Row> rit; String cv = ""; @@ -35,7 +35,7 @@ public class ExcelParser { row = (XSSFRow)rit.next(); rownum = row.getRowNum(); cv = row.getCell(TARGET_TYPE_CELL).getStringCellValue(); - if (rownum>= COUNTRIES_ROW && cv.equals(TARGET_TYPE)) + if (rownum >= COUNTRIES_ROW && cv.equals(TARGET_TYPE)) countries.add(read_country(row)); } wbook.close(); @@ -65,12 +65,6 @@ public class ExcelParser { fields[i++] = cell.getStringCellValue(); break; case NUMERIC: - /* - * We'll need another conversion back to - * Integer, but it's easier to do it that - * way than to keep lots of variables - * and hardcode everything. - */ n = dtoi(cell.getNumericCellValue()); fields[i++] = Integer.toString(n); break; diff --git a/java_development/population/src/population/Main.java b/java_development/population/src/population/Main.java @@ -7,10 +7,15 @@ public class Main extends Application { private final String APP_TITLE = "Population Statistics"; @Override - public void start(Stage stg) throws Exception { + public void start(Stage stg) { MainWindow mw = new MainWindow(stg, APP_TITLE); - - mw.setup(getParameters().getRaw().get(0)); + + try { + mw.setup(getParameters().getRaw().get(0)); + } catch (IndexOutOfBoundsException e) { + mw.err("usage: population xlsx_file"); + return; + } mw.show(); } diff --git a/java_development/population/src/population/MainWindow.java b/java_development/population/src/population/MainWindow.java @@ -49,9 +49,7 @@ public class MainWindow { } catch (FileNotFoundException e) { err(path + ": no such file"); return; - } catch (IndexOutOfBoundsException e) { - err("usage: population xlsx_file"); - return; + } catch (IOException e) { err(path + ": io error"); return; @@ -71,7 +69,7 @@ public class MainWindow { stg.show(); } - private void err(String errstr) { + public void err(String errstr) { Alert alert; alert = new Alert(AlertType.ERROR, errstr, ButtonType.CLOSE); @@ -86,11 +84,16 @@ public class MainWindow { btn_file = new Button("Load File"); btn_file.setOnAction(ev -> { + String path; FileChooser fc = new FileChooser(); + fc.setTitle("Load File"); fc.getExtensionFilters().add( new ExtensionFilter("XLSX Files", "*.xlsx")); - setup(fc.showOpenDialog(stg).getAbsolutePath()); + + path = fc.showOpenDialog(stg).getAbsolutePath(); + setup(path); + show(); }); btn_chart = new Button("Chart");