I am trying to play with JRuby with Java Swing : taking examples here and there, I am trying to translate them from pure Java to JRuby. That works well, especially after reading the part dedicated to Jruby in the excellent site Zetcode.
However there are things I still don't know how to translate.
For instance, picking this java code from Horstmann book, how could I translate correctly into JRuby ? In this code (in the Jpanel constructor), we rely on internal class for MouseAdapter. The rest is easy.
How to translate such internal (or more adequately 'anonymous') classes ?
just refactor/adjust parts to work out for you e.g.
frame = javax.swing.JFrame.new
frame.title = "MouseTest"
frame.set_size(300, 200)
frame.add_window_listener do |evt|
if evt.getID == java.awt.event.WindowEvent::WINDOW_CLOSING
java.lang.System.exit(0)
end
end
class MousePanel < Java::JavaxSwing::JPanel
SQUARELENGTH = 10; MAXNSQUARES = 100;
def initialize
super
#squares = []; #current = nil
add_mouse_listener self
add_mouse_motion_listener self
end
def add(x, y)
if #squares.size < MAXNSQUARES
#current = #squares.size
#squares << Point.new(x, y)
repaint
end
end
def remove(n)
return if (n < 0 || n >= #squares.size)
#squares.pop
#squares[n] = #squares[#squares.size];
#current = nil if #current == n
repaint
end
def paintComponent(graphics)
super
#squares.each { |square| do_draw(graphics, square) }
end
def do_draw(graphics, square)
graphics.drawRect(
square.x - SQUARELENGTH / 2,
square.y - SQUARELENGTH / 2,
SQUARELENGTH, SQUARELENGTH
)
end
private :do_draw
include java.awt.event.MouseListener
[ 'mouseEntered', 'mouseExited', 'mouseReleased' ].each do |method|
class_eval "def #{method}(evt); end"
end
def mousePressed(evt)
puts "mousePressed #{evt}"
end
def mouseClicked(evt)
puts "mouseClicked #{evt}"
end
include java.awt.event.MouseMotionListener
def mouseMoved(evt)
puts "mouseMoved #{evt}"
end
def mouseDragged(evt)
puts "mouseDragged #{evt}"
end
end
frame.content_pane.add MousePanel.new
frame.show
NOTE all the updated *add_xxx_listener occurrences ...
put this into a gist for readability/forkability (including the original code) : https://gist.github.com/kares/8538048
Related
I'm making an action RPG in Love2d, and I moved all of my player code into a separate Lua script for the sake of organization - but now upon attempting to use player.load, I get this error:
Error
main.lua:22: attempt to index global 'player' (a boolean value)
Traceback
main.lua:22: in function 'load'
[C]: in function 'xpcall'
[C]: in function 'xpcall'
this is my main.lua script:
-- RPG PROJECT IN LOVE2D
-- debug mode
debug = true -- SET FALSE BEFORE SHIPPING
-- ROOM CHART:
-- 0 - Title
-- 1 - Overworld
-- 2 - Shop
-- 3 - Boss Room
Room = 0
-- PLAYER STATE CHART:
-- 0 - free
-- 1 - attacking
-- 3 - can't move
function love.load(dt)
player = require 'Scripts/player'
player.load()
sti = require 'Libraries/sti'
gameMap = sti('Maps/overworld.lua')
menuImg = love.graphics.newImage('Assets/Menu.png')
love.window.setMode(800, 600)
end
function love.draw(dt)
if Room == 0 then
love.graphics.draw(menuImg, 0, 0)
end
if Room == 1 then
gameMap:draw()
player.draw()
end
if debug == true then
love.graphics.print("Current FPS: "..tostring(love.timer.getFPS( )), 10, 10)
end
end
function love.update(dt)
if Room == 0 then
if love.keyboard.isDown('space') then
Room = 1
end
end
if Room == 1 then
player.Update(dt)
if love.keyboard.isDown('escape') then
Room = 0
end
end
if love.keyboard.isDown('t') then
debug = not debug
end
end
and this is my player.lua script:
-- PLAYER SCRIPT
player = {x = 50, y = 50, speed = 3, state = 0, img = nil}
function player.load()
playerRight = love.graphics.newImage('Assets/playerRight.png')
playerLeft = love.graphics.newImage('Assets/playerLeft.png')
playerUp = love.graphics.newImage('Assets/playerUp.png')
playerDown = love.graphics.newImage('Assets/playerDown.png')
player.img = playerDown
end
function GetInput()
if player.state == 0 or player.state == 1 then
if love.keyboard.isDown('w') then
player.y = player.y - player.speed
player.img = playerUp
elseif love.keyboard.isDown('s') then
player.y = player.y + player.speed
player.img = playerDown
end
if love.keyboard.isDown('a') then
player.x = player.x - player.speed
player.img = playerLeft
elseif love.keyboard.isDown('d') then
player.x = player.x + player.speed
player.img = playerRight
end
end
end
function player.update(dt)
GetInput()
end
function player.draw()
love.graphics.draw(player.img, player.x, player.y)
end
Any help would be much appreciated!
Here's my folders as well, just in case it's a path issue:
UPDATE:
I solved it by renaming the player object to oPlayer, that was what was giving me an error.
I have severall Hints for you
Unclear
It looks you write it under Linux but should it also run under Windows?
If so, use OS independent folder delimiter ( the dot ) in require().
Example
player = require 'Scripts.player'
In General: Dont use Slash / Backslash \ in require()
Tip: LÖVE uses LuaJIT with modified Lua 5.1 check the Windows OS is easy
(For deciding to use Slash or Backslash (not in/for require()))
Clear
As #Luke100000 mentioned...
A Script for require has to return something.
If not than only a true wil be cached and a next require returns only true.
Therefore your player.lua content should be...
-- player.lua
local player = {x = 50, y = 50, speed = 3, state = 0, img = nil}
-- player.load() will be local
-- GetInput() wil be global
-- And the last line...
return(player)
I can set the prompt with _PROMPT = "> ", but can I make the prompt update every time?
I tried this, but it doesn't work:
i = 0
function inc()
i = i + 1
return i
end
_PROMPT = inc
This shows _PROMPT, but nothing related:
for k, v in pairs(_G) do
print(k)
end
The primary prompt is the value of the global variable _PROMPT, if this value is a string; otherwise, the default prompt is used.
https://www.lua.org/manual/5.1/lua.html
You assigned a function to _PROMPT.
I tried
_PROMPT = {no = 0}; setmetatable (_PROMPT, {__tostring = function (self) self.no = self.no + 1; return tostring (self.no) .. ' >' end})
, but no luck, although _PROMPT was incremented every time I typed = _PROMPT.
UPD Yet this can be done! In Lua mailing list I was advised to use
setmetatable(_ENV, {__index = function(t, k) if k == '_PROMPT' then t._N = (t._N or 0) + 1; return t._N .. ' >' end end})
It works. This effectively is a way to override any global in a deeper sense than simply to assign a new value to it, or make a set of global variables effectively infinite.
You can start Lua in interactive mode with a one-liner:
lua -i -e "setmetatable(_ENV, {__index = function(t, k) if k == '_PROMPT' then t._N = (t._N or 0) + 1; return t._N .. ' >' end end})"
I found out a method with debug.sethook().
It sounds a bit strange but it is really simple ;-)
Here we go...
# /usr/bin/lua -i
Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio
> debug.sethook(function(...) _PROMPT=os.date('%H:%M:%S # ') end,'r')
10:49:42 # -- Hiting ENTER some times
10:51:00 #
10:51:01 #
10:51:05 #
( Done with Lua 5.3.5 and tested 5.4 - Should work with 5.1 but not tested )
The 'r' means: Fire on each return
EDIT
Another way directly with _PROMPT and _PROMPT2...
-- Simple method for changing and/or time logging the prompts
_PROMPT=setmetatable({},{__index=table})
_PROMPT2=setmetatable({},{__index=table})
getmetatable(_PROMPT).__tostring=function()
_PROMPT:insert(os.date('%H:%M:%S',os.time())..'> ')
return string.format('%s',_PROMPT:concat(#_PROMPT,#_PROMPT))
end
getmetatable(_PROMPT2).__tostring=function()
_PROMPT2:insert(os.date('%H:%M:%S',os.time())..'>> ')
return string.format('%s',_PROMPT2:concat(#_PROMPT2,#_PROMPT2))
end
...using __tostring and __index has table metamethods.
...much fun and stay healthy.
I'm trying to get my save_game() method to work, nothing is getting written to the JSON file. This is my first assignment working with JSON and serialization in general. I'm not quite sure where I'm even going wrong.
These are my serialization methods:
def to_json
JSON.generate({array: #array, filestuff: #filestuff, random_word: #random_word, cipher: #cipher, random_word2: #random_word2, counter: #counter})
end
def load
game_file = File.read("saved.json")
data = JSON.parse(game_file)
#cipher = data["cipher"]
#random_word2 = data["random_word2"]
#counter = data["counter"]
end
def save_game(string)
game_file = File.new("saved.json","w")
game_file.write(string)
game_file.close
end
This is my program, on line 92 I try to call my save_game method.
require 'json'
load 'display.rb'
class Hangman
attr_accessor :name
#name = name
def initialize
puts "What is your name?"
#name = gets.chomp
puts "
################################################
HANGMAN
################################################
_________
|
| |
| O
| /|\\
| |
| / \\
|
-----------------
Welcome #{#name} to Hangman. The computer will generate
a 5-12 letter random word. You will try to guess
that word one letter at a time. Try to solve the
puzzle before time runs out!
"
end
end
class Gameplay
attr_accessor :array, :filestuff, :random_word, :cipher, :random_word2, :counter
def initialize
#array = []
#filestuff = File.foreach('5text.txt') do |x|
chomped = x.chomp
#array << chomped if (chomped.length >= 5 and chomped.length <= 12)
end
#random_word = #array.sample
#cipher = #random_word.gsub(/[a-z]/, '*').split(//)
#random_word2 = #random_word.split(//)
#counter = 5
def to_json
JSON.generate({array: #array, filestuff: #filestuff, random_word: #random_word, cipher: #cipher, random_word2: #random_word2, counter: #counter})
end
def load
game_file = File.read("saved.json")
data = JSON.parse(game_file)
#cipher = data["cipher"]
#random_word2 = data["random_word2"]
#counter = data["counter"]
end
def save_game(string)
game_file = File.new("saved.json","w")
game_file.write(string)
game_file.close
end
def choice(n)
#random_word2.each_with_index do |i,index|
if i == n
#cipher[index] = i
end
end
if n == #random_word2.join.to_s
puts "You win"
puts "would you like to start another game? Y/N"
new_game = gets.chomp
if new_game == "Y"
Hangman.new
else exit
end
end
if #random_word2.include?(n) == false
#counter -= 1
display
puts "#{#counter} guesses remaining."
puts "To save press 1"
save = gets.chomp
if save == "1"
#Will not save
save_game($b.to_json)
end
end
if #counter == 0
puts "would you like to start another game? Y/N"
new_game = gets.chomp
if new_game == "Y"
else exit
end
end
puts #cipher.join
end
#counter = 5
while #counter > 0
choice(gets.chomp)
end
end
end
Hangman.new
$b = Gameplay.new
You need to close the file in order to make sure your output is actually written to the disk ("flushed"). You can manually, call close:
def save_game(string)
game_file = File.new("saved.json","w")
game_file.write(string)
game_file.close
end
or, you can use File.open, which takes a block and closes the file when the block ends:
File.open("saved.json", "w") do |game_file|
game_file.write(string)
end
Since, writing to the disk is a slow operation, Ruby (and all languages that I can think of right now) will hold off on actually writing the file until it has accumulated a certain amount of text in a buffer. Once it has reached this limit, it will flush the buffer and write everything in it to disk. In order to make sure all your text is actually written when trying to write a file, you need to call close on the file, and as part of closing it, Ruby will flush whatever is left in its buffer.
There are other ways of making sure your content is flushed but when you're just starting to learn about this stuff, it should suffice to just make sure to always close files when you're done reading or writing them.
I have some trouble using jruby objects into java
java side
package com.pp;
public interface ZeroI {
boolean equals(Object o);
int hashCode();
int hash();
}
package com.pp;
public class Tester {
public Object[] compare(ZeroI one, ZeroI two) {
return new Object[] {one.hashCode(), two.hashCode(), one.equals(two), one == two};
}
}
jruby side
include Java
import com.pp.Tester
import com.pp.ZeroI
module MMM
module Zero
def hash= value
#hash = value
end
def hash
#hash
end
def hashCode
#hash
end
def equals other
false
end
def == other
true
end
end
class OneClass
include ZeroI
include Zero
end
class TwoClass
include ZeroI
include Zero
end
def self.create clazz
begin
dump = IO.readlines("C:/#{clazz.to_s.rpartition('::')[2]}.txt", '').to_s
instance = Marshal.load dump
rescue => err
puts err.message
instance = clazz.new
dump = Marshal.dump instance
File.open("C:/#{clazz.to_s.rpartition('::')[2]}.txt", 'w') { |f| f.write dump }
end
instance
end
tester = Tester.new
one = create OneClass
two = create TwoClass
puts one
puts two
one.hash = 22
two.hash = 22
puts one.hashCode
puts two.hashCode
puts one.equals two
puts one == two
tester.compare(one, two).each { |value| puts value }
end
First pass result:
No such file or directory - C:/OneClass.txt
No such file or directory - C:/TwoClass.txt
#<MMM::OneClass:0x1971eb3>
#<MMM::TwoClass:0x1408a75>
22
22
false
true
22
22
true
false
true # it's OK because JAVA.equals works with JRUBY.==
false # it's OK because org.pp.ZeroI can't declare == method and JAVA.== is used
Second pass result (with deserialized objects)
#<MMM::OneClass:0xd510e8>
#<MMM::TwoClass:0x490342>
22
22
false
true
13046738 # but what is it?
31877484 # but what is it?
false # but what is it?
false
Can anybody explain it?
I don't know all the details about why this happens like it happens but I have a solution/workaround for you. (I've seen similar behaviour when passing objects that are created on the ruby side to the Java side.)
As far as I can tell JRuby needs to have already "seen" an instance of the class it is trying to unmarshal before it can get the Java-inheritance side of things right. It's almost as if creating an object within JRuby has an undocumented side-effect that registers the required inheritance hierarchy. If that isn't well worded it's because I don't understand it myself!
So the workaround is to simply create an instance of OneClass and TwoClass before doing the unmarshal. If I change the self.create method to the following:
def self.create clazz
begin
clazz.new # <<< just create an instance and throw it away!
dump = IO.readlines("C:/#{clazz.to_s.rpartition('::')[2]}.txt", '').to_s
instance = Marshal.load dump
rescue => err
puts err.message
instance = clazz.new
dump = Marshal.dump instance
File.open("C:/#{clazz.to_s.rpartition('::')[2]}.txt", 'w') { |f| f.write dump }
end
instance
end
Then the output of the two passes are as follows:
First pass
No such file or directory - C:/OneClass.txt
No such file or directory - C:/TwoClass.txt
#<MMM::OneClass:0x4de6f0ef>
#<MMM::TwoClass:0x4526ba64>
22
22
false
true
22
22
true
false
Second pass
#<MMM::OneClass:0x4858cca9>
#<MMM::TwoClass:0x3de4905a>
22
22
false
true
22
22
true
false
According to this bug report this is scheduled to be fixed in JRuby 1.7. It's worth noting that while the comments in the report say that the workaround is to call a method, passing an object instance in, it seems to be that the prior creation of the object is enough.
I need to parse an HTML document to count the number of characters for both tags (including attributes) and text in Ruby. For performance reasons, I don't want to use a DOM parser. I've looked at Nokogiri's SAX and Reader parsers and also to SaxMachine, but neither seems to offer me a way to track the parser's position in the input HTML.
Does anyone know a way to access this information in Ruby? Thanks in advance
Input string
html = <<-HTML
<html>
<head>
<title>Title</title>
</head>
<body>
Hello world!
</body>
</html>
HTML
Dumb solution
Crude solution, it counts every alphabet character (ie. </html> count for 4 characters).
tag_count = 0
text_count = 0
in_tag = false
html.each_char do |char|
case char
when '<'
in_tag = true
when '>'
in_tag = false
when /\w/
in_tag ? tag_count += 1 : text_count += 1
end
end
puts "Text char count: #{text_count}"
puts "Tag char count: #{tag_count}"
Nokogiri SAX solution
This one could be easily translated to another language (eg. Java).
require 'nokogiri'
class HtmlCounter < Nokogiri::XML::SAX::Document
attr_accessor :tag_count, :text_count, :comment_count
def initialize(filtered_tags = [])
#filtered_tags = filtered_tags
end
def start_document
#tag_count = Hash.new(0)
#text_count = Hash.new(0)
#comment_count = 0
#current_tags = []
end
def start_element(name, attrs)
# Keep track of the nesting
#current_tags.push(name)
if should_count?
# Count the end element as well
count_tag(name.length * 2)
count_tag(attrs.flatten.map(&:length).inject(0) {|sum, length| sum + length})
end
end
def end_element(name)
#current_tags.pop
end
def comment(string)
count_comment(string.length) if should_count?
end
def characters(string)
count_text(string.strip.length) if should_count?
end
def should_count?
# Are we in a filtered tag ?
(#current_tags & #filtered_tags).empty?
end
def count_text(count)
#text_count[#current_tags.last] += count
end
def count_tag(count)
#tag_count[#current_tags.last] += count
end
def count_comment(count)
#comment_count[#current_tags.last] += count
end
end
# Don't count things in title tags
counter = HtmlCounter.new(["title"])
parser = Nokogiri::HTML::SAX::Parser.new(counter)
parser.parse(html)
puts "Text char count: #{counter.text_count}"
puts "Tag char count: #{counter.tag_count}"
output :
Text char count: {"body"=>12}
Tag char count: {"html"=>8, "head"=>8, "body"=>8}
Hope this helps.