skirmish 0.0.1
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.
- checksums.yaml +15 -0
- data/bin/skirmish +11 -0
- data/lib/skirmish.rb +93 -0
- data/lib/skirmish/character.rb +79 -0
- data/lib/skirmish/commands.rb +152 -0
- data/lib/skirmish/utilities.rb +186 -0
- data/lib/skirmish/world.rb +129 -0
- metadata +50 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
!binary "U0hBMQ==":
|
|
3
|
+
metadata.gz: !binary |-
|
|
4
|
+
MmI3OGUyNzQyMTE3NDcwOWFhNjFkNDZlZWRkMjkzZDNlMzQ1YWE0Yg==
|
|
5
|
+
data.tar.gz: !binary |-
|
|
6
|
+
OTY0NmQzMGNlMzhhMDMyNGVkYzFhMmJiZTdmNmRjYzlkNDljZDBkNA==
|
|
7
|
+
SHA512:
|
|
8
|
+
metadata.gz: !binary |-
|
|
9
|
+
NWIwZGY1MTE3ZjNjNmUzOTcwZTM5ZDYxYzFkMmY2ZDhjNmY5NjY5MmY2YjVm
|
|
10
|
+
YWE5ZmVlNjgyYzhkZmE4YTMzOWJhMmI2ZWVlM2U4ZmU3OTYwNjU2NTM2ODEx
|
|
11
|
+
NWNkZjRhYjhiM2U1NWYwMmNkYTY2MWI5YTg0MDE5ZTA4Y2VjZTg=
|
|
12
|
+
data.tar.gz: !binary |-
|
|
13
|
+
NjY3ZWVlZmM2MjgxZDAzMmU2MDIyNWM4ODAyMWI3Y2QyNzg3NTM2MDVmNWZj
|
|
14
|
+
MzdhMjU3ZjVlMzQzZDEwMzdhZTIxM2QwMTkxM2Q3NzM1NjZmZjkxYmU0YTY4
|
|
15
|
+
ZTEzYTYzYjg3Y2QzMWE0Mjg2Yzk3YzJjMDZlODhhODdjYmMzYjE=
|
data/bin/skirmish
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# skirmish is a single-player game in the style of CircleMUD.
|
|
4
|
+
# This script runs the game.
|
|
5
|
+
#
|
|
6
|
+
# Author:: Andy Mikula (mailto:andy@andymikula.ca)
|
|
7
|
+
# Copyright:: Copyright (c) 2016 Andy Mikula
|
|
8
|
+
# License:: MIT
|
|
9
|
+
|
|
10
|
+
require_relative '../lib/skirmish'
|
|
11
|
+
Skirmish.play
|
data/lib/skirmish.rb
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# skirmish is a single-player game in the style of CircleMUD.
|
|
2
|
+
# This file holds the main class and game loop.
|
|
3
|
+
#
|
|
4
|
+
# Author:: Andy Mikula (mailto:andy@andymikula.ca)
|
|
5
|
+
# Copyright:: Copyright (c) 2016 Andy Mikula
|
|
6
|
+
# License:: MIT
|
|
7
|
+
|
|
8
|
+
require 'curses'
|
|
9
|
+
require_relative 'skirmish/world'
|
|
10
|
+
require_relative 'skirmish/utilities'
|
|
11
|
+
require_relative 'skirmish/commands'
|
|
12
|
+
require_relative 'skirmish/character'
|
|
13
|
+
|
|
14
|
+
class Skirmish
|
|
15
|
+
|
|
16
|
+
# Set up the world and run the game loop
|
|
17
|
+
# TODO: Load the player / world state from a save file
|
|
18
|
+
def self.play
|
|
19
|
+
$world = World.new "lib/world/30.wld"
|
|
20
|
+
$player = Player.new
|
|
21
|
+
$input_buffer = Array.new
|
|
22
|
+
$command_history = Command_History.new
|
|
23
|
+
|
|
24
|
+
# TODO: Generate a number of mobiles that may or may not be a part of the
|
|
25
|
+
# player's quest. This shouldn't happen explicitly in the play method, but
|
|
26
|
+
# it does serve to demonstrate the functionality of the 'look' command
|
|
27
|
+
@mobile = Mobile.new("Bob", 1, 3014, "Bob is a character, just like you!", "bob")
|
|
28
|
+
|
|
29
|
+
# Set up the screen for curses and begin time
|
|
30
|
+
setup_screen
|
|
31
|
+
time = 0
|
|
32
|
+
|
|
33
|
+
# Main game loop
|
|
34
|
+
loop do
|
|
35
|
+
time += 1
|
|
36
|
+
|
|
37
|
+
case $player.state
|
|
38
|
+
# Player is entering their name
|
|
39
|
+
# TODO: Ask for confirmation on the name before continuing on.
|
|
40
|
+
when :CREATING
|
|
41
|
+
show_prompt("Welcome! What is your name?")
|
|
42
|
+
name = get_input
|
|
43
|
+
|
|
44
|
+
# TODO: Adding two newlines here is ugly.
|
|
45
|
+
unless name.nil?
|
|
46
|
+
$win.addstr("\n\n")
|
|
47
|
+
$player.name = name
|
|
48
|
+
cmd_stats($player, nil)
|
|
49
|
+
$player.state = :ROLLING
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Player is rolling stats
|
|
53
|
+
when :ROLLING
|
|
54
|
+
show_prompt("Is this acceptable (y/n)?")
|
|
55
|
+
choice = get_input
|
|
56
|
+
unless choice.nil?
|
|
57
|
+
$win.addstr("\n\n")
|
|
58
|
+
if choice =~ /\An\Z/i
|
|
59
|
+
$player.roll_stats
|
|
60
|
+
cmd_stats($player, nil)
|
|
61
|
+
elsif choice =~ /\Ay\Z/i
|
|
62
|
+
# Stats OK - set timeout for getch and show the player where
|
|
63
|
+
# they are in the world
|
|
64
|
+
$win.timeout = 100
|
|
65
|
+
$player.state = :PLAYING
|
|
66
|
+
cmd_look($player, "")
|
|
67
|
+
else
|
|
68
|
+
cmd_stats($player, nil)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Player is in the game, walking around
|
|
73
|
+
when :PLAYING
|
|
74
|
+
# TODO: Pick a reasonable length of time for a 'tick'
|
|
75
|
+
if time % 20 == 0
|
|
76
|
+
time = 0
|
|
77
|
+
# Tick! Do world stuff
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
show_prompt(">")
|
|
81
|
+
handle_input(get_input)
|
|
82
|
+
|
|
83
|
+
# Player is engaged in a fight
|
|
84
|
+
when :FIGHTING
|
|
85
|
+
# Nothing yet...
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
if __FILE__ == $0
|
|
91
|
+
play
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# skirmish is a single-player game in the style of CircleMUD.
|
|
2
|
+
# This file holds the Character class, as well as the Player and Mobile
|
|
3
|
+
# subclasses.
|
|
4
|
+
#
|
|
5
|
+
# Author:: Andy Mikula (mailto:andy@andymikula.ca)
|
|
6
|
+
# Copyright:: Copyright (c) 2016 Andy Mikula
|
|
7
|
+
# License:: MIT
|
|
8
|
+
|
|
9
|
+
# The Character class represents a being that lives in the world. Human players
|
|
10
|
+
# and NPCs are both 'Character's.
|
|
11
|
+
class Character
|
|
12
|
+
attr_accessor :name, :height, :weight, :str, :dex, :con, :int, :wis, :cha,
|
|
13
|
+
:maxhp, :hp, :xp, :armour, :level, :state, :location, :description, :keywords
|
|
14
|
+
|
|
15
|
+
# Create the character, which by default begins at level one and starts life
|
|
16
|
+
# at the temple (room 3001)
|
|
17
|
+
def initialize(name="", initial_level=1, initial_location=3001, description="", keywords=Array.new)
|
|
18
|
+
@state = :CREATING
|
|
19
|
+
@name = name
|
|
20
|
+
@description = description
|
|
21
|
+
@keywords = keywords
|
|
22
|
+
|
|
23
|
+
roll_stats initial_level
|
|
24
|
+
@location = initial_location
|
|
25
|
+
$world.move_character(self, 0, initial_location)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Roll stats for the character. This is based on the D&D 5e creation rules,
|
|
29
|
+
# with some tweaks.
|
|
30
|
+
def roll_stats(initial_level = 1)
|
|
31
|
+
size_roll = roll_dice(2, 10)
|
|
32
|
+
@height = 60 + size_roll #inches
|
|
33
|
+
@weight = 110 + roll_dice(2, 3) * size_roll #pounds
|
|
34
|
+
|
|
35
|
+
# Roll 3d6 for each attribute and sort the rolls by size
|
|
36
|
+
rolls = Array.new
|
|
37
|
+
6.times do
|
|
38
|
+
rolls.push roll_dice(3,6)
|
|
39
|
+
end
|
|
40
|
+
rolls.sort! { |a, b| a <=> b }
|
|
41
|
+
|
|
42
|
+
# Assign rolls in the standard order for a 'fighter'
|
|
43
|
+
@str = rolls.pop()
|
|
44
|
+
@con = rolls.pop()
|
|
45
|
+
@dex = rolls.pop()
|
|
46
|
+
@cha = rolls.pop()
|
|
47
|
+
@wis = rolls.pop()
|
|
48
|
+
@int = rolls.pop()
|
|
49
|
+
@maxhp = @con + 10
|
|
50
|
+
@hp = @maxhp
|
|
51
|
+
@xp = 0
|
|
52
|
+
@armour = @dex + 10
|
|
53
|
+
@level = initial_level
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Character's attack each time is calculated based on a roll of 1d10 * level,
|
|
57
|
+
# or as otherwise specified
|
|
58
|
+
def attack(num=1, size=10)
|
|
59
|
+
return roll_dice(num, size) * @level
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# A 'Player' is the human-controlled Character in the game
|
|
64
|
+
# TODO: Track playing time
|
|
65
|
+
class Player < Character
|
|
66
|
+
def initialize(name="", initial_level=1, initial_location=3001, description="You're...you!", keywords="self")
|
|
67
|
+
super(name, initial_level, initial_location, description, keywords)
|
|
68
|
+
@quest = nil
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# A 'Mobile' is any non-player character in the game
|
|
73
|
+
# TODO: Write 'act' method for Mobile to be called on game ticks (or some
|
|
74
|
+
# multiple thereof)
|
|
75
|
+
class Mobile < Character
|
|
76
|
+
def initialize(name="", initial_level=1, initial_location=3001, description="", keywords=Array.new)
|
|
77
|
+
super(name, initial_level, initial_location, description, keywords)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# skirmish is a single-player game in the style of CircleMUD.
|
|
2
|
+
# This file holds player commands.
|
|
3
|
+
#
|
|
4
|
+
# Author:: Andy Mikula (mailto:andy@andymikula.ca)
|
|
5
|
+
# Copyright:: Copyright (c) 2016 Andy Mikula
|
|
6
|
+
# License:: MIT
|
|
7
|
+
|
|
8
|
+
# Implementation of player commands. Each command must accept two arguments - a
|
|
9
|
+
# character object and the original input string
|
|
10
|
+
|
|
11
|
+
# TODO: Write commands as classes, so they can be aware of possible arguments
|
|
12
|
+
# and support tab-completion / notification to the user of incorrect arguments
|
|
13
|
+
|
|
14
|
+
# Move the character in the specified direction, if possible
|
|
15
|
+
def cmd_move_character(character, direction)
|
|
16
|
+
# Ask the world if there's a room in the direction we want to move
|
|
17
|
+
new_location = $world.get_destination(character.location,
|
|
18
|
+
case direction
|
|
19
|
+
when /\An/i then 0
|
|
20
|
+
when /\Ae/i then 1
|
|
21
|
+
when /\As/i then 2
|
|
22
|
+
when /\Aw/i then 3
|
|
23
|
+
when /\Au/i then 4
|
|
24
|
+
when /\Ad/i then 5
|
|
25
|
+
end
|
|
26
|
+
)
|
|
27
|
+
# If there's a room to go to, tell the world where the character is going, and
|
|
28
|
+
# let the character track it as well. If character is a player, either display
|
|
29
|
+
# their new location or a message telling them they are unable to move in that
|
|
30
|
+
# direction
|
|
31
|
+
unless new_location.nil?
|
|
32
|
+
$world.move_character character, character.location, new_location
|
|
33
|
+
character.location = new_location
|
|
34
|
+
if character.is_a?(Player)
|
|
35
|
+
cmd_look character, ""
|
|
36
|
+
end
|
|
37
|
+
else
|
|
38
|
+
if character.is_a?(Player)
|
|
39
|
+
print_line "You can't go that way!\n"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# List the available exits from the room the player is currently in
|
|
45
|
+
def cmd_list_exits(player, input)
|
|
46
|
+
exits = $world.get_exits player.location
|
|
47
|
+
|
|
48
|
+
exits_list = "[ Exits: "
|
|
49
|
+
until exits.empty? do
|
|
50
|
+
exits_list << "#{exits.shift} "
|
|
51
|
+
end
|
|
52
|
+
exits_list << "]\n"
|
|
53
|
+
|
|
54
|
+
print_line exits_list, :cyan
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# List the commands available to the player
|
|
58
|
+
# TODO: Pretty this output up a little bit - even columns would be nice
|
|
59
|
+
def cmd_list_commands(player, input)
|
|
60
|
+
commands = $commands.keys
|
|
61
|
+
|
|
62
|
+
print_line commands.join(" ")
|
|
63
|
+
print_line
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# For the Player's current location, display the room name, description, and any
|
|
67
|
+
# other characters who are in the room with the player
|
|
68
|
+
def cmd_look(player, input)
|
|
69
|
+
|
|
70
|
+
look_at = input.split[1..-1]
|
|
71
|
+
|
|
72
|
+
# If the player has specified a target / keyword, try to find it in the list
|
|
73
|
+
# of keywords on characters in the room
|
|
74
|
+
# TODO: Look at room keywords as well (i.e. 'look sign', etc.)
|
|
75
|
+
unless look_at.nil? || look_at.first.nil?
|
|
76
|
+
matches = $world.get_room_characters(player.location)
|
|
77
|
+
.select { |c| c.keywords =~ /\A#{Regexp.escape(look_at.first)}/i }
|
|
78
|
+
unless matches.first.nil?
|
|
79
|
+
print_line matches.first.description + "\n"
|
|
80
|
+
else
|
|
81
|
+
print_line "There is nothing to look at with that name.\n"
|
|
82
|
+
end
|
|
83
|
+
# No target specified - just show them the room name / description, any other
|
|
84
|
+
# characters who might be present, and the available exits
|
|
85
|
+
# TODO: Get the '...is standing here' description from the character
|
|
86
|
+
else
|
|
87
|
+
print_line $world.get_room_name(player.location), :cyan
|
|
88
|
+
print_line $world.get_room_description(player.location)
|
|
89
|
+
$world.get_room_characters(player.location).each do | char |
|
|
90
|
+
unless char.name == player.name
|
|
91
|
+
print_line char.name + " is standing here.", :white
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
cmd_list_exits player, nil
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Print the player's statistics to the screen
|
|
99
|
+
def cmd_stats(player, input)
|
|
100
|
+
|
|
101
|
+
# Height is stored in inches, but it's nice to have something like 5'10"
|
|
102
|
+
feet = player.height / 12
|
|
103
|
+
inches = player.height % 12
|
|
104
|
+
|
|
105
|
+
print_line("Your name is %s. You are %d'%d\" tall and you weigh %d lbs." %
|
|
106
|
+
[player.name, feet, inches, player.weight])
|
|
107
|
+
print_line("You are level %d and have %d experience points." %
|
|
108
|
+
[player.level, player.xp])
|
|
109
|
+
print_line("============================================================")
|
|
110
|
+
print_line("Hitpoints: %6d / %d" % [player.hp, player.maxhp])
|
|
111
|
+
print_line("Attack: %6d - %2d" % [player.level, player.level * 10])
|
|
112
|
+
print_line("Armour: %6d" % player.armour)
|
|
113
|
+
print_line("============================================================")
|
|
114
|
+
print_line("Strength: %6d Charisma: %6d" % [player.str, player.cha])
|
|
115
|
+
print_line("Constitution:%6d Wisdom: %6d" % [player.con, player.wis])
|
|
116
|
+
print_line("Dexterity: %6d Intelligence:%6d" % [player.dex, player.int])
|
|
117
|
+
print_line
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Quit the game. Make sure the player is sure! We don't want to quit on an
|
|
121
|
+
# accidental 'q', for example
|
|
122
|
+
# TODO: Save player / world state
|
|
123
|
+
def cmd_quit(player, input)
|
|
124
|
+
unless input =~ /quit/i
|
|
125
|
+
print_line "You must type the entire word 'quit' to quit.\n"
|
|
126
|
+
else
|
|
127
|
+
print_line "Until next time..."
|
|
128
|
+
$win.refresh
|
|
129
|
+
sleep 3
|
|
130
|
+
$win.close
|
|
131
|
+
exit
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# The possible command strings and their associated methods. To be able to use
|
|
136
|
+
# a new command, it must be implemented above and added here. Ruby remembers the
|
|
137
|
+
# order in which keys are added to a hash, which is convenient - we always match
|
|
138
|
+
# the first key we see. As a result, entries higher up in the list have greater
|
|
139
|
+
# precedence (making sure we match 'north' on 'n' instead of 'nap', for example)
|
|
140
|
+
$commands = { "north" => method(:cmd_move_character),
|
|
141
|
+
"east" => method(:cmd_move_character),
|
|
142
|
+
"south" => method(:cmd_move_character),
|
|
143
|
+
"west" => method(:cmd_move_character),
|
|
144
|
+
"up" => method(:cmd_move_character),
|
|
145
|
+
"down" => method(:cmd_move_character),
|
|
146
|
+
|
|
147
|
+
"commands" => method(:cmd_list_commands),
|
|
148
|
+
"exits" => method(:cmd_list_exits),
|
|
149
|
+
"look" => method(:cmd_look),
|
|
150
|
+
"stats" => method(:cmd_stats),
|
|
151
|
+
"quit" => method(:cmd_quit)
|
|
152
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# skirmish is a single-player game in the style of CircleMUD.
|
|
2
|
+
# This file holds the Command_History class, as well as functions for I/O, die
|
|
3
|
+
# rolling, and the like.
|
|
4
|
+
#
|
|
5
|
+
# Author:: Andy Mikula (mailto:andy@andymikula.ca)
|
|
6
|
+
# Copyright:: Copyright (c) 2016 Andy Mikula
|
|
7
|
+
# License:: MIT
|
|
8
|
+
|
|
9
|
+
# This class stores the commands that have been entered by the player, so we can
|
|
10
|
+
# have bash-style command history.
|
|
11
|
+
class Command_History
|
|
12
|
+
def initialize
|
|
13
|
+
@history = Array.new
|
|
14
|
+
@position = 0 # The current 'position' in the list of commands
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# The user entered a command - store it to the list and update the pointer
|
|
18
|
+
def add_command(command)
|
|
19
|
+
@history.push command
|
|
20
|
+
@position = @history.length - 1
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Unless we're already looking at the first item in the list, return the
|
|
24
|
+
# command at the current position and decrement the 'position' pointer
|
|
25
|
+
def get_previous
|
|
26
|
+
unless @position == 0
|
|
27
|
+
previous_command = @history[@position]
|
|
28
|
+
@position -= 1
|
|
29
|
+
return previous_command
|
|
30
|
+
else
|
|
31
|
+
return nil
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Unless we're already looking at the most recent item in the list, increment
|
|
36
|
+
# the 'position' pointer and return the command at the new position
|
|
37
|
+
def get_next
|
|
38
|
+
unless @position == @history.length - 1
|
|
39
|
+
@position += 1
|
|
40
|
+
next_command = @history[@position]
|
|
41
|
+
return next_command
|
|
42
|
+
else
|
|
43
|
+
return nil
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Look for input from the player, and return it once they have pressed 'enter'.
|
|
49
|
+
# Otherwise, return nil
|
|
50
|
+
def get_input
|
|
51
|
+
case char = $win.getch
|
|
52
|
+
# Backspace: Remove the last character from the input buffer
|
|
53
|
+
when Curses::KEY_BACKSPACE
|
|
54
|
+
$input_buffer.pop
|
|
55
|
+
return nil
|
|
56
|
+
|
|
57
|
+
# Up: Look for the previous command in the command history. If something
|
|
58
|
+
# exists, clear the input buffer and replace it with the last command
|
|
59
|
+
when Curses::KEY_UP
|
|
60
|
+
previous_command = $command_history.get_previous
|
|
61
|
+
unless previous_command.nil?
|
|
62
|
+
$input_buffer.clear
|
|
63
|
+
previous_command.each_char { |chr| $input_buffer.push chr }
|
|
64
|
+
end
|
|
65
|
+
return nil
|
|
66
|
+
|
|
67
|
+
# Down: Look for the next command in the command history. If something
|
|
68
|
+
# exists, clear the input buffer and replace it with the next command
|
|
69
|
+
when Curses::KEY_DOWN
|
|
70
|
+
next_command = $command_history.get_next
|
|
71
|
+
unless next_command.nil?
|
|
72
|
+
$input_buffer.clear
|
|
73
|
+
next_command.each_char { |chr| $input_buffer.push chr }
|
|
74
|
+
end
|
|
75
|
+
return nil
|
|
76
|
+
|
|
77
|
+
# Tab: See if there's a command that will match to what's currently in the
|
|
78
|
+
# input buffer. If there is, clear the buffer and replace with the command.
|
|
79
|
+
# If not, do nothing
|
|
80
|
+
when 9
|
|
81
|
+
possible_commands = $commands.select { |c| c =~ /\A#{Regexp.escape($input_buffer.join)}/i }
|
|
82
|
+
# TODO: Look for a space in the buffer instead of this horribe hack.
|
|
83
|
+
# We don't want to tab-complete the argument to a command
|
|
84
|
+
unless possible_commands.nil? or possible_commands.first.nil?
|
|
85
|
+
$input_buffer.clear
|
|
86
|
+
possible_commands.first.first.each_char { |chr| $input_buffer.push chr }
|
|
87
|
+
end
|
|
88
|
+
return nil
|
|
89
|
+
|
|
90
|
+
# Enter: Add the input buffer to the command history, and return the input
|
|
91
|
+
# as a string
|
|
92
|
+
when 10 , 13
|
|
93
|
+
input = $input_buffer.join
|
|
94
|
+
$input_buffer.clear
|
|
95
|
+
$command_history.add_command input
|
|
96
|
+
return input
|
|
97
|
+
|
|
98
|
+
# Any printable character: Add the character to the input buffer
|
|
99
|
+
when /[[:print:]]/
|
|
100
|
+
$input_buffer << char
|
|
101
|
+
return nil
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Curses screen setup and color pair definitions
|
|
106
|
+
def setup_screen
|
|
107
|
+
Curses.init_screen
|
|
108
|
+
Curses.start_color
|
|
109
|
+
Curses.cbreak
|
|
110
|
+
Curses.noecho
|
|
111
|
+
Curses.nl
|
|
112
|
+
|
|
113
|
+
# Color pairs. Arguments: color_number, foreground color, background color
|
|
114
|
+
Curses.init_pair 1, Curses::COLOR_GREEN, Curses::COLOR_BLACK
|
|
115
|
+
Curses.init_pair 2, Curses::COLOR_CYAN, Curses::COLOR_BLACK
|
|
116
|
+
Curses.init_pair 3, Curses::COLOR_WHITE, Curses::COLOR_BLACK
|
|
117
|
+
|
|
118
|
+
# Set up the window to fill the whole terminal
|
|
119
|
+
$win = Curses::Window.new(0, 0, 0, 0)
|
|
120
|
+
# Set the initial color to green on black
|
|
121
|
+
$win.color_set(1)
|
|
122
|
+
# Allow the screen to scroll, and allow the terminal to scroll as well
|
|
123
|
+
$win.scrollok true
|
|
124
|
+
$win.idlok true
|
|
125
|
+
# Allow capture of non-alpha key inputs (arrows, enter, etc.)
|
|
126
|
+
$win.keypad = true
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Find a command to invoke based on input from a character.
|
|
130
|
+
def handle_input(input)
|
|
131
|
+
return if input.nil?
|
|
132
|
+
|
|
133
|
+
input.chomp!
|
|
134
|
+
|
|
135
|
+
# Print functions clear the current line, so if we're handling input, we need
|
|
136
|
+
# to move down a few lines so we can see what we entered on our screen.
|
|
137
|
+
$win.addstr("\n\n")
|
|
138
|
+
|
|
139
|
+
if input.length == 0
|
|
140
|
+
print_line("Please enter a valid command. A list of commands is available by typing 'commands'.\n")
|
|
141
|
+
return nil
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# See if we can find a command key that matches the first word of the input
|
|
145
|
+
matches = $commands.select { |c| c =~ /\A#{Regexp.escape(input.split.first)}/i }
|
|
146
|
+
command = matches.first
|
|
147
|
+
|
|
148
|
+
unless command.nil?
|
|
149
|
+
# We have a match! Call the method associated with the command
|
|
150
|
+
return command[1].call($player, input)
|
|
151
|
+
else
|
|
152
|
+
print_line("\"#{input}\" is not a valid command. A list of commands is available by typing 'commands'.\n")
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Show a prompt to the player
|
|
157
|
+
def show_prompt(prompt=">")
|
|
158
|
+
$win.color_set(3)
|
|
159
|
+
$win.setpos($win.cury, 0)
|
|
160
|
+
$win.deleteln
|
|
161
|
+
$win.addstr(prompt + " " + $input_buffer.join)
|
|
162
|
+
$win.color_set(1)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Print a line to the screen.
|
|
166
|
+
def print_line(line = "", color = :green)
|
|
167
|
+
case color
|
|
168
|
+
when :green
|
|
169
|
+
$win.color_set(1)
|
|
170
|
+
when :cyan
|
|
171
|
+
$win.color_set(2)
|
|
172
|
+
when :white
|
|
173
|
+
$win.color_set(3)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
$win.setpos($win.cury, 0)
|
|
177
|
+
$win.deleteln
|
|
178
|
+
$win.addstr(line + "\n")
|
|
179
|
+
|
|
180
|
+
$win.color_set(1)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Roll number_of_dice with size_of_dice sides, and return the result
|
|
184
|
+
def roll_dice(number_of_dice, size_of_dice)
|
|
185
|
+
(1..number_of_dice).inject(0){ | sum, _ | sum + rand(1..size_of_dice) }
|
|
186
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# skirmish is a single-player game in the style of CircleMUD.
|
|
2
|
+
# This file holds the World class, as well as structures to hold room and
|
|
3
|
+
# description data.
|
|
4
|
+
#
|
|
5
|
+
# Author:: Andy Mikula (mailto:andy@andymikula.ca)
|
|
6
|
+
# Copyright:: Copyright (c) 2016 Andy Mikula
|
|
7
|
+
# License:: MIT
|
|
8
|
+
|
|
9
|
+
class World
|
|
10
|
+
|
|
11
|
+
# Create the world, using the specified file to load rooms.
|
|
12
|
+
# TODO: Load multiple room files from a folder
|
|
13
|
+
# TODO: Write world files in json or something similar
|
|
14
|
+
def initialize(world_file)
|
|
15
|
+
# Set up collections for rooms, characters, and potential mobile names
|
|
16
|
+
# TODO: Move @names to mobile class for use during creation
|
|
17
|
+
@rooms = Hash.new
|
|
18
|
+
@characters = Array.new
|
|
19
|
+
#@names = Array.new
|
|
20
|
+
|
|
21
|
+
# Not used currently
|
|
22
|
+
#f = File.open("lib/mobiles/names") or die "Unable to open 'names' file."
|
|
23
|
+
#f.each_line {|name| @names.push name}
|
|
24
|
+
#f.close
|
|
25
|
+
|
|
26
|
+
# Parse the CircleMUD .wld file. This is ugly, and I do not recommend
|
|
27
|
+
# touching it. Each room is given a name, description, and exits, and is
|
|
28
|
+
# added to the rooms list
|
|
29
|
+
# TODO: Get extra description data (signs, etc.)
|
|
30
|
+
File.read(world_file).split(/^\#/).drop(1).each do |chunk|
|
|
31
|
+
room = chunk.split(/\r?\n|\r/)
|
|
32
|
+
new_room = Room.new
|
|
33
|
+
|
|
34
|
+
room_number = room.shift.to_i
|
|
35
|
+
new_room.name = room.shift[0..-2]
|
|
36
|
+
|
|
37
|
+
new_room.description = room.shift
|
|
38
|
+
line = room.shift
|
|
39
|
+
until line == "~"
|
|
40
|
+
new_room.description.concat "\n"
|
|
41
|
+
new_room.description.concat line
|
|
42
|
+
line = room.shift
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
2.times {line = room.shift}
|
|
46
|
+
|
|
47
|
+
until line == "S"
|
|
48
|
+
if line[0] == "D"
|
|
49
|
+
direction = line[1]
|
|
50
|
+
dest = room.shift
|
|
51
|
+
dest = room.shift until dest == "~"
|
|
52
|
+
room.shift
|
|
53
|
+
dest = room.shift.split[-1]
|
|
54
|
+
new_room.direction_data.store(direction.to_i, dest.to_i)
|
|
55
|
+
line = room.shift
|
|
56
|
+
else
|
|
57
|
+
line = room.shift until line == "~"
|
|
58
|
+
line = room.shift
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
@rooms[room_number] = new_room
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# If we know about the room in question, return its name
|
|
66
|
+
def get_room_name(room_number)
|
|
67
|
+
@rooms.key?(room_number) ? @rooms[room_number].name : nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# If we know about the room in question, return its description
|
|
71
|
+
def get_room_description(room_number)
|
|
72
|
+
@rooms.key?(room_number) ? @rooms[room_number].description : nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# If we know about the room in question, and have data for the room in the
|
|
76
|
+
# specified direction, return the number of the destination room. This is
|
|
77
|
+
# likely a bit of an abuse of the ternary operator
|
|
78
|
+
def get_destination(room_number, direction)
|
|
79
|
+
@rooms.key?(room_number) && @rooms.key?(@rooms[room_number].direction_data[direction]) ? @rooms[room_number].direction_data[direction] : nil
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Return the list of characters who are currently in the specified room
|
|
83
|
+
def get_room_characters(room_number)
|
|
84
|
+
return @rooms[room_number].characters
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Move a character from original_location to new_location - each room's
|
|
88
|
+
# 'characters' list should contain each character who is currently there
|
|
89
|
+
def move_character(character, original_location, new_location)
|
|
90
|
+
if @rooms.key?(original_location)
|
|
91
|
+
@rooms[original_location].characters.delete(character)
|
|
92
|
+
end
|
|
93
|
+
@rooms[new_location].characters.push character
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Return an array of characters signifying the possible exits from the
|
|
97
|
+
# specified room
|
|
98
|
+
def get_exits(room_number)
|
|
99
|
+
directions = ["n", "e", "s", "w", "u", "d"]
|
|
100
|
+
exits = Array.new
|
|
101
|
+
|
|
102
|
+
6.times do |i|
|
|
103
|
+
if @rooms.key?(@rooms[room_number].direction_data[i])
|
|
104
|
+
exits.push(directions[i])
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
return exits
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# This does not need to be a whole class by itself, and is not currently used
|
|
112
|
+
# TODO: Make Room.extra_descriptions a hash, so we can store
|
|
113
|
+
# keyword => description pairs
|
|
114
|
+
#class DescriptionData
|
|
115
|
+
# @keyword
|
|
116
|
+
# @description
|
|
117
|
+
#end
|
|
118
|
+
|
|
119
|
+
# A class to represent a room. A room is uniquely identified by its number,
|
|
120
|
+
# knows which characters and objects are in it, and knows which rooms are
|
|
121
|
+
# connected to it and in which direction
|
|
122
|
+
class Room
|
|
123
|
+
attr_accessor :number, :name, :description, :direction_data, :characters
|
|
124
|
+
|
|
125
|
+
def initialize
|
|
126
|
+
@direction_data = Hash.new
|
|
127
|
+
@characters = Array.new
|
|
128
|
+
end
|
|
129
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: skirmish
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Andy Mikula
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2016-03-17 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: A single-player text-based game compatible with CircleMUD world files
|
|
14
|
+
email: andy@andymikula.ca
|
|
15
|
+
executables:
|
|
16
|
+
- skirmish
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- bin/skirmish
|
|
21
|
+
- lib/skirmish.rb
|
|
22
|
+
- lib/skirmish/character.rb
|
|
23
|
+
- lib/skirmish/commands.rb
|
|
24
|
+
- lib/skirmish/utilities.rb
|
|
25
|
+
- lib/skirmish/world.rb
|
|
26
|
+
homepage: http://andy-j.github.io/skirmish
|
|
27
|
+
licenses:
|
|
28
|
+
- MIT
|
|
29
|
+
metadata: {}
|
|
30
|
+
post_install_message:
|
|
31
|
+
rdoc_options: []
|
|
32
|
+
require_paths:
|
|
33
|
+
- lib
|
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ! '>='
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '0'
|
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ! '>='
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: '0'
|
|
44
|
+
requirements: []
|
|
45
|
+
rubyforge_project:
|
|
46
|
+
rubygems_version: 2.4.8
|
|
47
|
+
signing_key:
|
|
48
|
+
specification_version: 4
|
|
49
|
+
summary: skirmish
|
|
50
|
+
test_files: []
|