chess_cli 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.config/locale/debug_en.yml +4 -0
  3. data/.config/locale/en.yml +424 -0
  4. data/bin/chess_cli +6 -0
  5. data/lib/chess_cli.rb +11 -0
  6. data/lib/console/console.rb +202 -0
  7. data/lib/console/wait_utils.rb +25 -0
  8. data/lib/console_game/base_game.rb +74 -0
  9. data/lib/console_game/chess/board.rb +110 -0
  10. data/lib/console_game/chess/game.rb +184 -0
  11. data/lib/console_game/chess/input/algebraic_notation.rb +103 -0
  12. data/lib/console_game/chess/input/chess_input.rb +191 -0
  13. data/lib/console_game/chess/input/smith_notation.rb +38 -0
  14. data/lib/console_game/chess/launcher.rb +20 -0
  15. data/lib/console_game/chess/level.rb +276 -0
  16. data/lib/console_game/chess/logics/display.rb +182 -0
  17. data/lib/console_game/chess/logics/endgame_logic.rb +126 -0
  18. data/lib/console_game/chess/logics/logic.rb +137 -0
  19. data/lib/console_game/chess/logics/moves_simulation.rb +75 -0
  20. data/lib/console_game/chess/logics/piece_analysis.rb +76 -0
  21. data/lib/console_game/chess/logics/piece_lookup.rb +93 -0
  22. data/lib/console_game/chess/pieces/bishop.rb +18 -0
  23. data/lib/console_game/chess/pieces/chess_piece.rb +204 -0
  24. data/lib/console_game/chess/pieces/king.rb +200 -0
  25. data/lib/console_game/chess/pieces/knight.rb +46 -0
  26. data/lib/console_game/chess/pieces/pawn.rb +142 -0
  27. data/lib/console_game/chess/pieces/queen.rb +16 -0
  28. data/lib/console_game/chess/pieces/rook.rb +37 -0
  29. data/lib/console_game/chess/player/chess_computer.rb +25 -0
  30. data/lib/console_game/chess/player/chess_player.rb +211 -0
  31. data/lib/console_game/chess/utilities/chess_utils.rb +67 -0
  32. data/lib/console_game/chess/utilities/fen_export.rb +114 -0
  33. data/lib/console_game/chess/utilities/fen_import.rb +196 -0
  34. data/lib/console_game/chess/utilities/load_manager.rb +51 -0
  35. data/lib/console_game/chess/utilities/pgn_export.rb +97 -0
  36. data/lib/console_game/chess/utilities/pgn_utils.rb +134 -0
  37. data/lib/console_game/chess/utilities/player_builder.rb +74 -0
  38. data/lib/console_game/chess/utilities/session_builder.rb +48 -0
  39. data/lib/console_game/chess/version.rb +8 -0
  40. data/lib/console_game/console_menu.rb +68 -0
  41. data/lib/console_game/game_manager.rb +181 -0
  42. data/lib/console_game/input.rb +87 -0
  43. data/lib/console_game/player.rb +100 -0
  44. data/lib/console_game/user_profile.rb +65 -0
  45. data/lib/nimbus_file_utils/nimbus_file_utils.rb +194 -0
  46. data/user_data/.keep +0 -0
  47. data/user_data/dummy_user.json +124 -0
  48. data/user_data/pgn_export/.keep +0 -0
  49. metadata +147 -0
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "chess_utils"
4
+ require_relative "../player/chess_player"
5
+ require_relative "../player/chess_computer"
6
+
7
+ module ConsoleGame
8
+ module Chess
9
+ # The Player Builder class helps create new chess players for each session
10
+ # @author Ancient Nimbus
11
+ class PlayerBuilder
12
+ include ChessUtils
13
+
14
+ attr_reader :game, :session, :controller, :human_class, :ai_class
15
+
16
+ # @param game [Game] expects chess game manager object
17
+ def initialize(game)
18
+ @game = game
19
+ @controller = game.controller
20
+ @human_class = ChessPlayer
21
+ @ai_class = ChessComputer
22
+ end
23
+
24
+ # Create players based on load mode
25
+ # @param session [Hash] game session to load
26
+ # @return [Array<ChessPlayer, ChessComputer>]
27
+ def build_players(session)
28
+ @session = session
29
+ case session[:mode]
30
+ when 1 then pvp_mode
31
+ when 2 then pve_mode
32
+ else raise KeyError
33
+ end
34
+ end
35
+
36
+ # Create a player
37
+ # @param name [String] expects a name
38
+ # @param side [Symbol, nil] expects :black or :white
39
+ # @param type [Symbol] expects :human or :ai
40
+ # @param m_history [Array<String>]
41
+ # @return [ChessPlayer, ChessComputer]
42
+ def create_player(name = "", side = nil, type: :human, m_history: [])
43
+ new_player = type == :human ? human_class : ai_class
44
+ new_player.new(name, controller, side, m_history:)
45
+ end
46
+
47
+ private
48
+
49
+ # When it is player vs player
50
+ # @return [Array<ChessPlayer>]
51
+ def pvp_mode
52
+ SIDES_SYM.map { |side| create_player(session[side], side, m_history: m_history_data(side)) }
53
+ end
54
+
55
+ # When it is player vs computer
56
+ # @return [Array<ChessPlayer, ChessComputer>]
57
+ def pve_mode
58
+ computer_side = session.key("Computer")
59
+ raise KeyError if computer_side.nil?
60
+
61
+ player_side = opposite_of(computer_side)
62
+ [create_player(session[player_side], player_side, m_history: m_history_data(player_side)),
63
+ create_player(session[computer_side], computer_side, type: :ai, m_history: m_history_data(computer_side))]
64
+ end
65
+
66
+ # Helper to return moves history depending on side
67
+ # @return [Array<String>]
68
+ def m_history_data(side)
69
+ data = session["#{side}_moves".to_sym]
70
+ data.nil? ? [] : data
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "chess_utils"
4
+
5
+ module ConsoleGame
6
+ module Chess
7
+ # The Session Builder class helps create new session data hash
8
+ # @author Ancient Nimbus
9
+ class SessionBuilder
10
+ include ChessUtils
11
+
12
+ # Create session data
13
+ # @return [Array<Integer, Hash>] session data
14
+ def self.build_session(...) = new(...).build_session
15
+
16
+ attr_reader :sessions, :mode, :sides, :site_txt, :ongoing_txt
17
+
18
+ # @param game [Game] expects chess game manager object
19
+ def initialize(game)
20
+ @sessions = game.sessions
21
+ @mode = game.mode
22
+ @sides = game.sides
23
+ @site_txt = game.s("misc.site")
24
+ @ongoing_txt = game.s("status.ongoing")
25
+ end
26
+
27
+ # Build session data
28
+ # @return [Array<Integer, Hash>] session data
29
+ def build_session
30
+ id = sessions.size + 1
31
+ set_player_side
32
+ wp_name, bp_name = sides.values_at(w_sym, b_sym).map(&:name)
33
+ session = { mode: mode,
34
+ white: wp_name,
35
+ black: bp_name,
36
+ event: "#{wp_name} vs #{bp_name} Status #{ongoing_txt}",
37
+ site: site_txt,
38
+ date: Time.new.ceil.strftime(STR_TIME) }
39
+ [id, session]
40
+ end
41
+
42
+ private
43
+
44
+ # Set internal side value for new player object
45
+ def set_player_side = sides.map { |side, player| player.side = side }
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConsoleGame
4
+ module Chess
5
+ # Chess game version
6
+ VER = "0.9.2"
7
+ end
8
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "input"
4
+
5
+ module ConsoleGame
6
+ # Input controller for console menu
7
+ class ConsoleMenu < Input
8
+ # == Console Commands ==
9
+
10
+ # Exit sequences | command patterns: `exit`, `ttfn`
11
+ def quit(_args = [])
12
+ print_msg(s("cli.lobby.exit"))
13
+ super
14
+ end
15
+
16
+ # Display help string | command pattern: `help`
17
+ def help(_args = []) = print_msg(s("cli.help"))
18
+
19
+ # Display system info | command pattern: `info`
20
+ def info(_args = []) = print_msg(s("cli.menu"))
21
+
22
+ # Save user profile to disk | command pattern: `save`
23
+ def save(_args = []) = game_manager.save_user_profile
24
+
25
+ # Load user profile from disk | command pattern: `load`
26
+ def load(_args = []) = game_manager.switch_user_profile
27
+
28
+ # Launch a game | command pattern: `play <launch code>`
29
+ # @param args [Array<String>] optional arguments
30
+ def play(args = [])
31
+ return print_msg(s("cli.play.gm_err")) unless game_manager
32
+
33
+ app = args[0]
34
+ game_manager.apps.key?(app) ? game_manager.launch(app) : print_msg(s("cli.play.run_err"), pre: "! ")
35
+ end
36
+
37
+ # Display user info | command pattern: `self`
38
+ def self(_args = [])
39
+ profile = game_manager.user.profile
40
+ user_color = :yellow
41
+ # p "Debug: #{profile}"
42
+ print_msg(s("cli.self.msg",
43
+ { uuid: [profile[:uuid], user_color],
44
+ date: [profile[:saved_date].strftime("%m/%d/%Y %I:%M %p"), user_color],
45
+ name: [profile[:username], user_color],
46
+ visit: [profile[:stats][:launch_count], user_color] }))
47
+ end
48
+
49
+ private
50
+
51
+ # Easter egg | command pattern: `lol`
52
+ def lol(_args = [])
53
+ Whirly.start spinner: "random_dots", status: s("cli.lol.title").sample do
54
+ sleep 3
55
+ Whirly.status = s("cli.lol.sub").sample
56
+ sleep 1.5
57
+ msgs = s("cli.lol.msgs")
58
+ print_msg(msgs[command_usage[:lol] % msgs.size], pre: "⠗ ")
59
+ end
60
+ end
61
+
62
+ # == Unities ==
63
+
64
+ # Setup input commands
65
+ def setup_commands = super.merge({ "save" => method(:save), "load" => method(:load), "play" => method(:play),
66
+ "self" => method(:self), "lol" => method(:lol) })
67
+ end
68
+ end
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../nimbus_file_utils/nimbus_file_utils"
4
+ require_relative "../console/console"
5
+ require_relative "input"
6
+ require_relative "console_menu"
7
+ require_relative "user_profile"
8
+ require_relative "player"
9
+ require_relative "base_game"
10
+ require_relative "chess/launcher"
11
+
12
+ # Console Game System v2.0.0
13
+ # @author Ancient Nimbus
14
+ # @version 2.0.0
15
+ module ConsoleGame
16
+ # Game Manager for Console game
17
+ class GameManager
18
+ include Console
19
+ include ::NimbusFileUtils
20
+ include Chess::Launcher
21
+
22
+ # Alias for NimbusFileUtils
23
+ F = NimbusFileUtils
24
+
25
+ attr_reader :base_input, :cli, :apps, :user, :ver
26
+ attr_accessor :running, :active_game
27
+
28
+ def initialize(lang: "en")
29
+ F.set_locale(lang)
30
+ @ver = "2.0.0"
31
+ @running = true
32
+ @base_input = Input.new(self)
33
+ @cli = ConsoleMenu.new(self)
34
+ @apps = load_app_list
35
+ @user = nil
36
+ @active_game = nil
37
+ end
38
+
39
+ # run the console game manager
40
+ def run
41
+ greet
42
+ assign_user_profile
43
+ lobby
44
+ end
45
+
46
+ # Run a game
47
+ # @param app [String]
48
+ def launch(app)
49
+ self.active_game = apps[app].call(self)
50
+ active_game.start
51
+ print_msg(s(shut_down_msg(app)))
52
+ self.active_game = nil
53
+ end
54
+
55
+ # Exit Arcade
56
+ def exit_arcade
57
+ self.running = false
58
+ user&.save_profile
59
+ exit
60
+ end
61
+
62
+ # Save user profile
63
+ # @param mute [Boolean] bypass printing when use at the background
64
+ def save_user_profile(mute: false)
65
+ return load_err(:no_profile2) if user.nil?
66
+
67
+ user.save_profile
68
+ print_msg(s("cli.save.msg", { dir: [user.filepath, :yellow] })) unless mute
69
+ end
70
+
71
+ # Load another profile when using is at the lobby
72
+ def switch_user_profile
73
+ return load_err(:no_profile2) if user.nil?
74
+
75
+ @user = load_profile
76
+ launch_counter
77
+ end
78
+
79
+ private
80
+
81
+ # Greet user
82
+ def greet
83
+ tf_fetcher("", *%w[.guideline .boot], root: "cli").each do |msg|
84
+ print_msg(msg.sub("0.0.0", ver))
85
+ base_input.ask(s("cli.std_blank"), empty: true)
86
+ end
87
+ end
88
+
89
+ # Arcade lobby
90
+ def lobby
91
+ print_msg(s("cli.menu"))
92
+ cli.ask(empty: true) while running
93
+ end
94
+
95
+ # Define the app list
96
+ def load_app_list = {}.merge(load_chess)
97
+
98
+ # End game message
99
+ # @param app [String] expects app name
100
+ # @return [String] textfile key
101
+ def shut_down_msg(app) = "app.#{app}.end.home"
102
+
103
+ # Setup user profile
104
+ def assign_user_profile
105
+ print_msg(s("cli.new.msg"))
106
+ mode = base_input.ask(s("cli.new.msg2"), reg: [1, 2], input_type: :range).to_i
107
+ @user = mode == 1 ? new_profile : load_profile
108
+ # Create stats data
109
+ launch_counter
110
+ # Save to disk
111
+ save_user_profile
112
+ end
113
+
114
+ # Handle new user
115
+ # @param username [String] username for UserProfile, skip prompt stage if it is provided
116
+ # @return [UserProfile] user profile
117
+ def new_profile(username = "")
118
+ # Get username
119
+ username = username.empty? ? grab_username : username
120
+ # Create user profile
121
+ profile = UserProfile.new(username)
122
+ # Welcome user
123
+ print_msg(s("cli.new.msg4", { name: [profile.username, :yellow] }))
124
+ profile
125
+ end
126
+
127
+ # Handle returning user
128
+ # @param extname [String] extension name
129
+ # @return [UserProfile] user profile
130
+ def load_profile(extname: ".json")
131
+ f_path = select_profile(extname: extname)
132
+ # Load ops
133
+ profile = f_path.nil? ? load_err(:no_profile) : UserProfile.load_profile(F.load_file(f_path, extname: extname))
134
+ profile = profile.nil? ? load_err(:bad_profile) : UserProfile.new(profile[:username], profile)
135
+ # Post-load ops
136
+ print_msg(s("cli.load.msg3", { name: [profile.username, :yellow] }))
137
+ profile
138
+ end
139
+
140
+ # Handle profile selection
141
+ # @param extname [String] extension name
142
+ # @return [String] filepath
143
+ def select_profile(extname: ".json")
144
+ print_msg(s("cli.load.msg"))
145
+
146
+ folder_path = F.filepath("", "user_data")
147
+ profiles = F.file_list(folder_path, extname: extname)
148
+ return nil if profiles.empty?
149
+
150
+ # Print the list
151
+ print_msg(*build_file_list(folder_path, profiles, col1: s("cli.load.li_col1"), col2: s("cli.load.li_col2")))
152
+ # Handle selection
153
+ profile = base_input.pick_from(profiles, msg: s("cli.load.msg2"), err_msg: s("cli.load.input_err"))
154
+ # Returns a valid filename
155
+ folder_path + profile
156
+ end
157
+
158
+ # Edge cases handling when there are issue loading a profile
159
+ # @param err_label [Symbol] control which error message to print
160
+ # @return [UserProfile]
161
+ def load_err(err_label = :no_profile)
162
+ err_msgs ||= { no_profile: "cli.load.no_profile_err", no_profile2: "cli.load.no_profile_err2",
163
+ bad_profile: "cli.load.bad_file_err" }
164
+ keypath = err_msgs.fetch(err_label, "cli.load.unknown_err")
165
+ print_msg(s(keypath), pre: "! ")
166
+ new_profile
167
+ end
168
+
169
+ # Get username from prompt
170
+ # @return [String] username
171
+ def grab_username = base_input.ask(s("cli.new.msg3"), reg: COMMON_REG[:filename], input_type: :custom, empty: true)
172
+
173
+ # Simple usage stats counting
174
+ def launch_counter
175
+ return load_err(:no_profile2) if user.nil?
176
+
177
+ user.profile[:stats][:launch_count] ||= 0
178
+ user.profile[:stats][:launch_count] += 1
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "whirly"
4
+ require "paint"
5
+ require_relative "../nimbus_file_utils/nimbus_file_utils"
6
+ require_relative "../console/console"
7
+
8
+ module ConsoleGame
9
+ # Input class is a base class for various control layouts for ConsoleGame
10
+ class Input
11
+ include Console
12
+ include ::NimbusFileUtils
13
+
14
+ attr_reader :game_manager, :commands, :cmd_pattern, :command_usage
15
+
16
+ # @param game_manager [ConsoleGame::GameManager]
17
+ def initialize(game_manager = nil)
18
+ @game_manager = game_manager
19
+ @commands = setup_commands
20
+ @cmd_pattern = regexp_capturing_gp(commands.keys, pre: "--", suf: '(\s.*)?')
21
+ @command_usage = Hash.new { |h, k| h[k] = 0 }
22
+ end
23
+
24
+ # == Core methods ==
25
+
26
+ # Override: process user input
27
+ # @param msg [String] first print
28
+ # @param cmds [Hash] expects a list of commands hash
29
+ # @param err_msg [String] second print
30
+ # @param reg [Regexp, String, Array<String>] pattern to match, use an Array when input type is :range
31
+ # @param empty [Boolean] allow empty input value, default to false
32
+ # @param input_type [Symbol] expects the following option: :any, :range, :custom
33
+ # @return [String]
34
+ def ask(msg = "", cmds: commands, err_msg: s("cli.std_err"), reg: ".*", empty: false, input_type: :any)
35
+ reg = case input_type
36
+ when :range then regexp_range(cmd_pattern, min: reg[0], max: reg[1])
37
+ when :custom then regexp_formatter(cmd_pattern, reg)
38
+ else reg
39
+ end
40
+ # p "location: #{self.class}, reg: #{reg}"
41
+ # p reg
42
+ super(msg, cmds:, err_msg:, reg:, empty:)
43
+ end
44
+
45
+ # Process user input where bound checks are required
46
+ # @param options [Array] a list of options
47
+ # @param msg [String] first print
48
+ # @param err_msg [String] second print
49
+ # @return [Any] a valid element within the given array
50
+ def pick_from(options, msg: "Pick from the following options: ", err_msg: "Not a valid option, try again.")
51
+ until options.fetch(opt = ask(msg, reg: COMMON_REG[:digits], input_type: :custom, err_msg: err_msg).to_i - 1, nil)
52
+ print_msg(err_msg, pre: Paint["! ", :red])
53
+ end
54
+ options[opt]
55
+ end
56
+
57
+ # == Console Commands ==
58
+
59
+ # Exit sequences | command patterns: `exit`
60
+ def quit(_args = []) = game_manager.nil? ? exit : game_manager.exit_arcade
61
+
62
+ # Display help string | command pattern: `help`
63
+ def help(_args = []) = print_msg(s("cli.std_help"))
64
+
65
+ # Display system info | command pattern: `info`
66
+ def info(_args = []) = print_msg(s("cli.ver", { ver: game_manager.ver }))
67
+
68
+ private
69
+
70
+ # == Unities ==
71
+
72
+ # Setup input commands
73
+ def setup_commands = { "exit" => method(:quit), "ttfn" => method(:quit), "help" => method(:help),
74
+ "info" => method(:info) }
75
+
76
+ # Override: Handle command
77
+ # @param cmd [String]
78
+ # @param opt_arg [String]
79
+ # @param cmds [Array]
80
+ # @param is_valid [Boolean]
81
+ # @param cmd_err [String] custom error message
82
+ def handle_command(cmd, opt_arg, cmds, is_valid, cmd_err: s("cli.cmd_err"))
83
+ command_usage[cmd.to_sym] += 1 if cmds.key?(cmd)
84
+ super
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "paint"
4
+
5
+ module ConsoleGame
6
+ # Player class
7
+ class Player
8
+ @number_of_player = 0
9
+ # @colors = String.colors[8..15]
10
+ class << self
11
+ # attr_reader :colors
12
+
13
+ # Add Player count
14
+ def add_player
15
+ @number_of_player += 1
16
+ end
17
+
18
+ # Set global player count
19
+ def player_count(value)
20
+ @number_of_player = value
21
+ end
22
+
23
+ # Return number of active players
24
+ def total_player
25
+ @number_of_player
26
+ end
27
+
28
+ # Setup color array and remove unwanted color options
29
+ # def setup_color
30
+ # %i[default gray].each { |elem| remove_color(elem) }
31
+ # end
32
+
33
+ # Remove color option to avoid two players share the same color tag
34
+ # @param color [Symbol]
35
+ # def remove_color(color)
36
+ # @colors.delete(color)
37
+ # end
38
+ end
39
+
40
+ attr_reader :data, :name, :player_color, :controller
41
+
42
+ # @param name [String]
43
+ # @param controller [Input]
44
+ def initialize(name = "", controller = nil)
45
+ @name = name
46
+ @controller = controller
47
+ # Player.setup_color
48
+ Player.add_player
49
+ @player_color = Paint.random
50
+ edit_name(name)
51
+ init_data
52
+ end
53
+
54
+ # Initialise player save data
55
+ def init_data
56
+ @data = { name: name }
57
+ end
58
+
59
+ # Edit player name
60
+ # @param new_name [String]
61
+ def edit_name(new_name = "")
62
+ if new_name.empty?
63
+ new_name = name.empty? ? "Player #{Player.total_player}" : name
64
+ end
65
+ @name = Paint[new_name, player_color]
66
+ end
67
+
68
+ # Store player's move
69
+ # @param value [Integer] Positional value of the grid
70
+ # def store_move(value)
71
+ # return nil if value.nil?
72
+
73
+ # data.fetch(:moves) << value
74
+ # end
75
+
76
+ # Update player turn count
77
+ # def update_turn_count
78
+ # data[:turn] = data.fetch(:moves).size
79
+ # end
80
+
81
+ # == Utilities ==
82
+ end
83
+
84
+ # Computer player class
85
+ # class Computer < Player
86
+ # def initialize(game_manager = nil, name = "Computer")
87
+ # super(game_manager, name)
88
+ # end
89
+
90
+ # # Returns a random integer between 1 to 7
91
+ # # @param empty_slots [Array<Integer>]
92
+ # # @param bound [Array<Integer>]
93
+ # def random_move(empty_slots, bound)
94
+ # row, = bound
95
+ # value = (empty_slots.sample % row) + 1
96
+ # print "#{value}\n"
97
+ # value
98
+ # end
99
+ # end
100
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require_relative "../nimbus_file_utils/nimbus_file_utils"
5
+
6
+ module ConsoleGame
7
+ # User Profile class
8
+ class UserProfile
9
+ include ::NimbusFileUtils
10
+
11
+ # Alias for NimbusFileUtils
12
+ F = NimbusFileUtils
13
+ # Expected user profile structure
14
+ PROFILE = { uuid: "", username: "", saved_date: Time, appdata: {}, stats: {} }.freeze
15
+
16
+ class << self
17
+ # load user profile
18
+ def load_profile(user_profile, _extname: ".json")
19
+ integrity_check(user_profile)
20
+ end
21
+
22
+ private
23
+
24
+ # Profile integrity check
25
+ # @param imported_profile [Hash]
26
+ def integrity_check(imported_profile)
27
+ return nil unless imported_profile.keys == PROFILE.keys
28
+
29
+ imported_profile[:saved_date] = to_time(imported_profile[:saved_date])
30
+ imported_profile
31
+ end
32
+
33
+ # convert string time field to TIme object for internal use
34
+ # @param str_date [String] expects a valid date format
35
+ def to_time(str_date)
36
+ return str_date if str_date.is_a?(Time)
37
+
38
+ Time.new(str_date)
39
+ end
40
+ end
41
+
42
+ attr_reader :profile, :filepath
43
+ attr_accessor :username
44
+
45
+ def initialize(username = "", user_profile = nil)
46
+ @username = username.empty? ? "Arcade Player" : username
47
+ @profile = user_profile.nil? ? create_profile : user_profile
48
+ end
49
+
50
+ # Create a user profile
51
+ def create_profile
52
+ { uuid: SecureRandom.uuid, username: username, saved_date: Time.now.ceil,
53
+ appdata: Hash.new { |hash, key| hash[key] = {} }, stats: {} }
54
+ end
55
+
56
+ # Save user profile
57
+ def save_profile(extname: ".json")
58
+ filename ||= F.formatted_filename(username, extname)
59
+ @filepath ||= F.filepath(filename, "user_data")
60
+
61
+ profile[:saved_date] = Time.now.ceil
62
+ F.write_to_disk(filepath, profile)
63
+ end
64
+ end
65
+ end