Mercurial > hg > charamega
changeset 0:91ecd24948de
Imported.
author | Mikhail Kryshen <mikhail@kryshen.net> |
---|---|
date | Sat, 14 Jul 2012 05:46:29 +0400 |
parents | |
children | fac1b8f35265 |
files | .hgignore Rakefile res/net/kryshen/charamega/DejaVuSans.ttf src/net/kryshen/charamega/card.mirah src/net/kryshen/charamega/field.mirah src/net/kryshen/charamega/game.mirah src/net/kryshen/charamega/ui.mirah |
diffstat | 7 files changed, 1010 insertions(+), 0 deletions(-) [+] |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/.hgignore Sat Jul 14 05:46:29 2012 +0400 1.3 @@ -0,0 +1,5 @@ 1.4 +syntax: regexp 1.5 + 1.6 +~$ 1.7 +^build$ 1.8 +^local_
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 2.2 +++ b/Rakefile Sat Jul 14 05:46:29 2012 +0400 2.3 @@ -0,0 +1,15 @@ 2.4 +require 'mirah' 2.5 +require 'rake/clean' 2.6 + 2.7 +SRC = FileList['src/**/*.mirah'] 2.8 +CLEAN.include('build/**/*.class') 2.9 + 2.10 +task :default => [:compile] 2.11 + 2.12 +task :compile => SRC do 2.13 + Mirah::compile '-d', 'build', *SRC 2.14 +end 2.15 + 2.16 +task :run => [:compile] do 2.17 + sh 'java -cp build:res net/kryshen/charamega/Ui' 2.18 +end
3.1 Binary file res/net/kryshen/charamega/DejaVuSans.ttf has changed
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 4.2 +++ b/src/net/kryshen/charamega/card.mirah Sat Jul 14 05:46:29 2012 +0400 4.3 @@ -0,0 +1,100 @@ 4.4 +# 4.5 +# Copyright 2012 Mikhail Kryshen <mikhail@kryshen.net> 4.6 +# 4.7 +# This file is part of Charamega. 4.8 +# 4.9 +# Charamega is free software: you can redistribute it and/or modify 4.10 +# it under the terms of the GNU General Public License as published by 4.11 +# the Free Software Foundation, either version 3 of the License, or 4.12 +# (at your option) any later version. 4.13 +# 4.14 +# Charamega is distributed in the hope that it will be useful, 4.15 +# but WITHOUT ANY WARRANTY; without even the implied warranty of 4.16 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 4.17 +# GNU General Public License for more details. 4.18 +# 4.19 +# You should have received a copy of the GNU General Public License 4.20 +# along with Charamega. If not, see <http://www.gnu.org/licenses/>. 4.21 +# 4.22 + 4.23 +package net.kryshen.charamega 4.24 + 4.25 +class Card 4.26 + 4.27 + def initialize(symbol:char, time:long) 4.28 + @symbol = symbol 4.29 + @angle = Math.random * Math.PI - Math.PI / 2 4.30 + 4.31 + @flip_interval = long(2E8) 4.32 + @remove_interval = long(2E8) 4.33 + 4.34 + @close_time = time - @flip_interval 4.35 + @match_time = @open_time = @close_time - 1 4.36 + end 4.37 + 4.38 + def symbol 4.39 + @symbol 4.40 + end 4.41 + 4.42 + def angle 4.43 + @angle 4.44 + end 4.45 + 4.46 + def open(time:long):void 4.47 + @open_time = time unless opened? 4.48 + end 4.49 + 4.50 + def close(time:long):void 4.51 + @close_time = time if opened? 4.52 + end 4.53 + 4.54 + def match(time:long):void 4.55 + @match_time = time unless matched? 4.56 + end 4.57 + 4.58 + def opened? 4.59 + @close_time - @open_time <= 0 4.60 + end 4.61 + 4.62 + def matched? 4.63 + @close_time - @match_time <= 0 4.64 + end 4.65 + 4.66 + # -1.0 - closed, 1.0 - opened. 4.67 + def flip_state(time:long) 4.68 + if opened? 4.69 + s = 1.0 4.70 + else 4.71 + s = -1.0 4.72 + end 4.73 + 4.74 + t = Math.max(1, time - Math.max(@close_time, @open_time)) 4.75 + 4.76 + return s if t >= @flip_interval 4.77 + 4.78 + s * (t * 2 - @flip_interval) / double(@flip_interval) 4.79 + end 4.80 + 4.81 + # 1.0 - visible, 0.0 - removed. 4.82 + def visible_state(time:long) 4.83 + return 1.0 if !matched? 4.84 + 4.85 + t = Math.max(1, time - @match_time - @flip_interval / 2) 4.86 + Math.max(0.0, 1.0 - t / double(@remove_interval)) 4.87 + end 4.88 + 4.89 + def auto_close?(time:long) 4.90 + opened? and time - @open_time > 2E9 4.91 + end 4.92 + 4.93 + def toString 4.94 + String.valueOf(@symbol) + 4.95 + if matched? 4.96 + '[matched]' 4.97 + elsif opened? 4.98 + '[opened]' 4.99 + else 4.100 + '' 4.101 + end 4.102 + end 4.103 +end
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 5.2 +++ b/src/net/kryshen/charamega/field.mirah Sat Jul 14 05:46:29 2012 +0400 5.3 @@ -0,0 +1,243 @@ 5.4 +# 5.5 +# Copyright 2012 Mikhail Kryshen <mikhail@kryshen.net> 5.6 +# 5.7 +# This file is part of Charamega. 5.8 +# 5.9 +# Charamega is free software: you can redistribute it and/or modify 5.10 +# it under the terms of the GNU General Public License as published by 5.11 +# the Free Software Foundation, either version 3 of the License, or 5.12 +# (at your option) any later version. 5.13 +# 5.14 +# Charamega is distributed in the hope that it will be useful, 5.15 +# but WITHOUT ANY WARRANTY; without even the implied warranty of 5.16 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 5.17 +# GNU General Public License for more details. 5.18 +# 5.19 +# You should have received a copy of the GNU General Public License 5.20 +# along with Charamega. If not, see <http://www.gnu.org/licenses/>. 5.21 +# 5.22 + 5.23 +package net.kryshen.charamega 5.24 + 5.25 +import java.awt.* 5.26 +import java.awt.geom.* 5.27 +import java.awt.event.* 5.28 +import javax.swing.* 5.29 +import java.util.List 5.30 + 5.31 +class Field < JComponent 5.32 + 5.33 + def initialize(game:Game) 5.34 + @field_color = Color.WHITE 5.35 + @border_color = Color.GRAY 5.36 + @active_border_color = Color.DARK_GRAY 5.37 + @symbol_color = Color.BLACK 5.38 + @back_color = Color.LIGHT_GRAY 5.39 + @face_color = Color.new(0xFFFDDD) 5.40 + 5.41 + @game = game 5.42 + 5.43 + setOpaque true 5.44 + setDoubleBuffered true 5.45 + 5.46 + begin 5.47 + source = getClass.getResource("DejaVuSans.ttf").openStream 5.48 + font = Font.createFont(Font.TRUETYPE_FONT, source) 5.49 + ensure 5.50 + source.close unless source.nil? 5.51 + end 5.52 + setFont font 5.53 + 5.54 + enableEvents AWTEvent.MOUSE_EVENT_MASK 5.55 + enableEvents AWTEvent.MOUSE_MOTION_EVENT_MASK 5.56 + end 5.57 + 5.58 + def processMouseEvent(event) 5.59 + if isEnabled 5.60 + if event.getID == MouseEvent.MOUSE_PRESSED 5.61 + 5.62 + @hold = hit(event.getX, event.getY) 5.63 + 5.64 + elsif event.getID == MouseEvent.MOUSE_RELEASED and 5.65 + !@hold.nil? and @hold == hit(event.getX, event.getY) 5.66 + 5.67 + @game.open @hold 5.68 + repaint 5.69 + elsif event.getID == MouseEvent.MOUSE_EXITED and 5.70 + !@hovered.nil? 5.71 + 5.72 + @hovered = nil 5.73 + repaint 5.74 + end 5.75 + end 5.76 + 5.77 + super 5.78 + end 5.79 + 5.80 + def processMouseMotionEvent(event) 5.81 + if isEnabled 5.82 + h = hit(event.getX, event.getY) 5.83 + 5.84 + if event.getID == MouseEvent.MOUSE_DRAGGED 5.85 + if h == @hold and h != @hovered 5.86 + @hovered = h 5.87 + repaint 5.88 + end 5.89 + 5.90 + if h != @hold and @hold == @hovered 5.91 + @hovered = nil 5.92 + repaint 5.93 + end 5.94 + 5.95 + elsif h != @hovered 5.96 + @hovered = h 5.97 + repaint 5.98 + end 5.99 + end 5.100 + 5.101 + super 5.102 + end 5.103 + 5.104 + def paintComponent(g1) 5.105 + g = Graphics2D(g1) 5.106 + 5.107 + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 5.108 + RenderingHints.VALUE_ANTIALIAS_ON) 5.109 + g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, 5.110 + RenderingHints.VALUE_FRACTIONALMETRICS_ON) 5.111 + 5.112 + w = getWidth 5.113 + h = getHeight 5.114 + 5.115 + g.setColor @field_color 5.116 + g.fillRect 0, 0, w, h 5.117 + 5.118 + insets = getInsets 5.119 + w -= insets.left + insets.right 5.120 + h -= insets.top + insets.bottom 5.121 + 5.122 + layout = @game.layout(w, h) 5.123 + time = System.nanoTime 5.124 + 5.125 + max_cols = Math.ceil(double(@game.cards.size) / layout.size) 5.126 + rh = double(h) / layout.size 5.127 + card_size = Math.min(rh, double(w) / max_cols) / 1.4 5.128 + font_size = card_size 5.129 + font = g.getFont.deriveFont(float(font_size)) 5.130 + frc = g.getFontRenderContext 5.131 + 5.132 + rm_scale = 2.0 5.133 + 5.134 + outer = Rectangle2D.Double.new 5.135 + inner = Rectangle2D.Double.new 5.136 + border = Path2D.Double.new(Path2D.WIND_EVEN_ODD) 5.137 + 5.138 + save_t = g.getTransform 5.139 + 5.140 + y = rh / 2 + insets.top 5.141 + layout.each do |row| 5.142 + cw = double(w) / List(row).size 5.143 + x = cw / 2 + insets.left 5.144 + 5.145 + List(row).each do |e| 5.146 + card = Card(e) 5.147 + 5.148 + v = card.visible_state(time) 5.149 + 5.150 + # Compute area potentially affected by the card. 5.151 + if v < 1.0 5.152 + paint_x = int(Math.floor(x - cw * rm_scale / 2 - 1)) 5.153 + paint_y = int(Math.floor(y - rh * rm_scale / 2 - 1)) 5.154 + paint_w = int(Math.ceil(cw * rm_scale + 2)) 5.155 + paint_h = int(Math.ceil(rh * rm_scale + 2)) 5.156 + else 5.157 + paint_x = int(Math.floor(x - cw / 2 - 1)) 5.158 + paint_y = int(Math.floor(y - rh / 2 - 1)) 5.159 + paint_w = int(Math.ceil(cw + 2)) 5.160 + paint_h = int(Math.ceil(rh + 2)) 5.161 + end 5.162 + 5.163 + if v > 0.0 and g.hitClip(paint_x, paint_y, paint_w, paint_h) 5.164 + f = card.flip_state(time) 5.165 + 5.166 + if v < 1.0 or (f > -1.0 and f < 1.0) 5.167 + # Animation is in progress. 5.168 + repaint 30, paint_x, paint_y, paint_w, paint_h 5.169 + end 5.170 + 5.171 + g.translate x, y 5.172 + 5.173 + v_scale = rm_scale - Math.sqrt(v) * rm_scale 5.174 + g.scale Math.abs(f) + v_scale, 1.0 + v_scale 5.175 + 5.176 + g.rotate card.angle * v 5.177 + 5.178 + outer.setRect(-card_size / 2, -card_size / 2, 5.179 + card_size, card_size) 5.180 + inner.setRect(1 - card_size / 2, 1 - card_size / 2, 5.181 + card_size - 2, card_size - 2) 5.182 + border.reset 5.183 + border.append outer, false 5.184 + border.append inner, false 5.185 + 5.186 + if f > 0 5.187 + g.setColor with_alpha(@face_color, v) 5.188 + else 5.189 + g.setColor with_alpha(@back_color, v) 5.190 + end 5.191 + 5.192 + g.fill inner 5.193 + 5.194 + if f > 0 5.195 + g.setColor with_alpha(@symbol_color, Math.sqrt(v)) 5.196 + 5.197 + gv = font.createGlyphVector(frc, String.valueOf(card.symbol)) 5.198 + sb = gv.getVisualBounds 5.199 + 5.200 + # Scale down if the glyph does not fit. 5.201 + tolerance = 0.95 5.202 + scale = Math.min(inner.getWidth * tolerance / sb.getWidth, 5.203 + inner.getHeight * tolerance / sb.getHeight) 5.204 + save_t_2 = g.getTransform 5.205 + g.scale scale, scale if scale < 1.0 5.206 + 5.207 + g.drawGlyphVector(gv, 5.208 + -float(sb.getX + sb.getWidth / 2), 5.209 + -float(sb.getY + sb.getHeight / 2)) 5.210 + 5.211 + g.setTransform save_t_2 5.212 + end 5.213 + 5.214 + if card == @hovered 5.215 + g.setColor with_alpha(@active_border_color, v) 5.216 + else 5.217 + g.setColor with_alpha(@border_color, v) 5.218 + end 5.219 + 5.220 + g.fill border 5.221 + 5.222 + g.setTransform save_t 5.223 + end 5.224 + 5.225 + x += cw 5.226 + end 5.227 + 5.228 + y += rh 5.229 + end 5.230 + end 5.231 + 5.232 + private 5.233 + 5.234 + def hit(x:int, y:int) 5.235 + insets = getInsets 5.236 + size = getSize 5.237 + @game.hit(x - insets.left, 5.238 + y - insets.top, 5.239 + size.width - insets.left - insets.right, 5.240 + size.height - insets.top - insets.bottom) 5.241 + end 5.242 + 5.243 + def with_alpha(c:Color, alpha:double) 5.244 + Color.new c.getRed, c.getGreen, c.getBlue, int(alpha * 255) 5.245 + end 5.246 +end
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 6.2 +++ b/src/net/kryshen/charamega/game.mirah Sat Jul 14 05:46:29 2012 +0400 6.3 @@ -0,0 +1,384 @@ 6.4 +# 6.5 +# Copyright 2012 Mikhail Kryshen <mikhail@kryshen.net> 6.6 +# 6.7 +# This file is part of Charamega. 6.8 +# 6.9 +# Charamega is free software: you can redistribute it and/or modify 6.10 +# it under the terms of the GNU General Public License as published by 6.11 +# the Free Software Foundation, either version 3 of the License, or 6.12 +# (at your option) any later version. 6.13 +# 6.14 +# Charamega is distributed in the hope that it will be useful, 6.15 +# but WITHOUT ANY WARRANTY; without even the implied warranty of 6.16 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 6.17 +# GNU General Public License for more details. 6.18 +# 6.19 +# You should have received a copy of the GNU General Public License 6.20 +# along with Charamega. If not, see <http://www.gnu.org/licenses/>. 6.21 +# 6.22 + 6.23 +package net.kryshen.charamega 6.24 + 6.25 +import java.util.Collection 6.26 +import java.util.List 6.27 +import java.util.ArrayList 6.28 +import java.util.Collections 6.29 + 6.30 +class Game 6.31 + 6.32 + def initialize 6.33 + @symbols = ArrayList.new 6.34 + 6.35 + add_range 0x03B1, 0x03C9 # greek 6.36 + add_all [0x0439, 0x044A, 0x0463, 0x0467] # cyrillic 6.37 + add_range 0x20A0, 0x20B5 # currency 6.38 + add_all [0x2190, 0x2194, 0x21CC] # arrows 6.39 + add_all [0x221A, 0x221E, 0x222B, 0x222E] # math 6.40 + add_all [0x2318, 0x23CE, 0x23CF, 0x23E3] 6.41 + add_all [0x25A8, 0x25A9] 6.42 + add_range 0x25C6, 0x25D0 6.43 + add_all [0x25D4] 6.44 + add_range 0x2600, 0x261A 6.45 + add_range 0x2620, 0x2630 6.46 + add_range 0x2638, 0x2672 6.47 + add_range 0x267A, 0x2689 6.48 + add_range 0x2690, 0x269C 6.49 + add_all [0x26A0, 0x26A1] 6.50 + add_all [0x2706, 0x2707, 0x2708, 0x2709] 6.51 + add_all [0x270C, 0x270D, 0x270E] 6.52 + add_all [0x2744] 6.53 + 6.54 + self.players = 1 6.55 + end 6.56 + 6.57 + def shuffle 6.58 + return false if @shuffled 6.59 + shuffle @cards.size / 2 6.60 + 6.61 + true 6.62 + end 6.63 + 6.64 + def shuffle(npairs:int):void 6.65 + Collections.shuffle @symbols 6.66 + 6.67 + cards = ArrayList.new npairs 6.68 + time = System.nanoTime 6.69 + 6.70 + @symbols.subList(0, npairs).each do |c| 6.71 + cards.add Card.new Character(c).charValue, time 6.72 + cards.add Card.new Character(c).charValue, time 6.73 + end 6.74 + 6.75 + Collections.shuffle cards 6.76 + @cards = cards 6.77 + @layout = List(nil) 6.78 + @first = @second = Card(nil) 6.79 + @non_matching = 0 6.80 + @seconds = 0 6.81 + @shuffled = true 6.82 + 6.83 + compute_limits 6.84 + end 6.85 + 6.86 + def players=(nplayers:int):void 6.87 + @matches = int[nplayers] 6.88 + @player = 0 6.89 + end 6.90 + 6.91 + def cards 6.92 + @cards 6.93 + end 6.94 + 6.95 + def start:void 6.96 + shuffle 6.97 + @shuffled = false 6.98 + 6.99 + @matches.length.times { |i| @matches[i] = 0 } 6.100 + @player = 0 6.101 + 6.102 + @start_time = System.nanoTime 6.103 + @playing = true 6.104 + end 6.105 + 6.106 + def stop:void 6.107 + @playing = false 6.108 + 6.109 + unless @shuffled 6.110 + time = System.nanoTime 6.111 + delay = long(2E8 / @cards.size) 6.112 + 6.113 + @layout.each do |row| 6.114 + List(row).each do |e| 6.115 + card = Card(e) 6.116 + card.open time unless card.matched? 6.117 + time += delay 6.118 + end 6.119 + end 6.120 + end 6.121 + end 6.122 + 6.123 + # Returns list of lists of cards for each row. 6.124 + def layout(w:int, h:int):List 6.125 + return @layout unless @layout.nil? or @shuffled 6.126 + 6.127 + n = @cards.size 6.128 + cols = columns(float(w) / h) 6.129 + rows = int(Math.ceil float(n) / cols) 6.130 + 6.131 + if !@layout.nil? and 6.132 + @layout_cols == cols and 6.133 + @layout_rows == rows 6.134 + 6.135 + return @layout 6.136 + end 6.137 + 6.138 + layout = ArrayList.new rows 6.139 + 6.140 + d = cols * rows - n 6.141 + c = 0 6.142 + rows.times do |i| 6.143 + k = cols 6.144 + k -= 1 if i >= rows - d 6.145 + 6.146 + row = ArrayList.new k 6.147 + k.times do 6.148 + row.add @cards.get(c) 6.149 + c += 1 6.150 + end 6.151 + 6.152 + layout.add row 6.153 + end 6.154 + 6.155 + Collections.shuffle layout 6.156 + 6.157 + @layout_cols = cols 6.158 + @layout_rows = rows 6.159 + @layout = layout 6.160 + end 6.161 + 6.162 + # Called periodically by UI. 6.163 + def update 6.164 + time = System.nanoTime 6.165 + @seconds = int((time - @start_time) / 1E9) 6.166 + 6.167 + # Flip the cards back after some time. 6.168 + if !@second.nil? and 6.169 + @first.auto_close?(time) and @second.auto_close?(time) 6.170 + 6.171 + @first.close time 6.172 + @second.close time 6.173 + return true 6.174 + end 6.175 + 6.176 + false 6.177 + end 6.178 + 6.179 + # Number of removed pairs. 6.180 + def matched 6.181 + matched = 0 6.182 + @matches.each { |i| matched += i } 6.183 + 6.184 + matched 6.185 + end 6.186 + 6.187 + def finished? 6.188 + ignore_limits = false 6.189 + # ignore_limits = true 6.190 + 6.191 + matched * 2 == @cards.size or 6.192 + (!multiplayer? and !ignore_limits and 6.193 + (@non_matching > @max_non_matching or 6.194 + @seconds >= @max_seconds)) 6.195 + end 6.196 + 6.197 + def winner 6.198 + max = -1 6.199 + winner = -1 6.200 + 6.201 + @matches.length.times do |i| 6.202 + if @matches[i] > max 6.203 + max = @matches[i] 6.204 + winner = i 6.205 + elsif @matches[i] == max 6.206 + winner = -1 6.207 + end 6.208 + end 6.209 + 6.210 + winner 6.211 + end 6.212 + 6.213 + def multiplayer? 6.214 + @matches.length > 1 6.215 + end 6.216 + 6.217 + def status 6.218 + if multiplayer? 6.219 + sb = StringBuffer.new 6.220 + sb.append 'Scores: ' 6.221 + @matches.length.times do |i| 6.222 + sb.append ', ' if i > 0 6.223 + sb.append '(' if i == @player 6.224 + sb.append String.valueOf(@matches[i]) 6.225 + sb.append ')' if i == @player 6.226 + end 6.227 + sb.append ". " 6.228 + 6.229 + if finished? 6.230 + w = winner 6.231 + sb.append "Player #{w + 1} wins!" if w >= 0 6.232 + sb.append "Tie!" if w < 0 6.233 + else 6.234 + sb.append "Player #{@player + 1}'s turn." 6.235 + end 6.236 + 6.237 + sb.toString 6.238 + else 6.239 + sec = @max_seconds - @seconds 6.240 + time = Integer[2] 6.241 + time[0] = Integer.valueOf(sec / 60) 6.242 + time[1] = Integer.valueOf(sec % 60) 6.243 + 6.244 + sb = StringBuffer.new 6.245 + sb.append String.format("Time left: %02d:%02d.", time) 6.246 + sb.append " Non-matching: #{@non_matching}" 6.247 + sb.append " / #{@max_non_matching}." 6.248 + 6.249 + left = @cards.size / 2 - matched 6.250 + 6.251 + if left == 0 6.252 + sb.append " You win!" 6.253 + puts "#{@cards.size / 2},#{@non_matching},#{@seconds}" 6.254 + else 6.255 + sb.append " Pars left: #{left}." 6.256 + end 6.257 + 6.258 + sb.toString 6.259 + end 6.260 + end 6.261 + 6.262 + def open(card:Card):boolean 6.263 + return false if finished? 6.264 + return false if card.matched? 6.265 + return false if card.opened? and (@first.nil? or @second.nil?) 6.266 + 6.267 + time = System.nanoTime 6.268 + 6.269 + if @first.nil? 6.270 + @first = card.open time 6.271 + elsif @second.nil? 6.272 + @second = card.open time 6.273 + else 6.274 + @first.close time 6.275 + @second.close time 6.276 + @first = card.open time 6.277 + @second = nil 6.278 + end 6.279 + 6.280 + unless @second.nil? 6.281 + if @first.symbol == @second.symbol 6.282 + @first.match time 6.283 + @second.match time 6.284 + @first = @second = nil 6.285 + @matches[@player] += 1 6.286 + else 6.287 + @non_matching += 1 6.288 + @player = (@player + 1) % @matches.length 6.289 + end 6.290 + end 6.291 + 6.292 + true 6.293 + end 6.294 + 6.295 + def hit(x:int, y:int, w:int, h:int) 6.296 + return nil if x < 0 or y < 0 6.297 + 6.298 + layout = layout(w, h) 6.299 + i = y * layout.size / h 6.300 + 6.301 + return nil if i >= layout.size 6.302 + 6.303 + row = List(layout.get i) 6.304 + j = x * row.size / w 6.305 + 6.306 + return nil if j >= row.size 6.307 + 6.308 + Card(row.get j) 6.309 + end 6.310 + 6.311 + private 6.312 + 6.313 + def compute_limits 6.314 + pairs = @cards.size / 2 6.315 + 6.316 + @max_non_matching = 0 6.317 + @max_seconds = 2 6.318 + 6.319 + # No need to optimize this. 6.320 + pairs.downto(3) do |i| 6.321 + if i > 180 6.322 + @max_non_matching += 9 6.323 + elsif i > 150 6.324 + @max_non_matching += 8 6.325 + elsif i > 120 6.326 + @max_non_matching += 7 6.327 + elsif i > 90 6.328 + @max_non_matching += 6 6.329 + elsif i > 60 6.330 + @max_non_matching += 5 6.331 + elsif i > 30 6.332 + @max_non_matching += 4 6.333 + elsif i > 20 6.334 + @max_non_matching += 3 6.335 + elsif i > 8 6.336 + @max_non_matching += 2 6.337 + else 6.338 + @max_non_matching += 1 6.339 + end 6.340 + 6.341 + if i > 160 6.342 + @max_seconds += 30 6.343 + elsif i > 120 6.344 + @max_seconds += 25 6.345 + elsif i > 80 6.346 + @max_seconds += 20 6.347 + elsif i > 60 6.348 + @max_seconds += 15 6.349 + elsif i > 40 6.350 + @max_seconds += 12 6.351 + elsif i > 20 6.352 + @max_seconds += 9 6.353 + elsif i > 9 6.354 + @max_seconds += 6 6.355 + elsif i > 5 6.356 + @max_seconds += 4 6.357 + else 6.358 + @max_seconds += 2 6.359 + end 6.360 + end 6.361 + end 6.362 + 6.363 + def columns(aspect:float) 6.364 + n = @cards.size 6.365 + c = Math.max(1, int(Math.ceil Math.sqrt(n * aspect))) 6.366 + 6.367 + loop do 6.368 + m = n % c 6.369 + break if m == 0 or c - m <= n / c 6.370 + c -= 1 6.371 + end 6.372 + 6.373 + c 6.374 + end 6.375 + 6.376 + def add_range(from:int, to:int):void 6.377 + from.upto(to) do |i| 6.378 + @symbols.add Character.new char(i) 6.379 + end 6.380 + end 6.381 + 6.382 + def add_all(xs:Collection) 6.383 + xs.each do |i| 6.384 + @symbols.add Character.new char(Number(i).intValue) 6.385 + end 6.386 + end 6.387 +end
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 7.2 +++ b/src/net/kryshen/charamega/ui.mirah Sat Jul 14 05:46:29 2012 +0400 7.3 @@ -0,0 +1,263 @@ 7.4 +# 7.5 +# Copyright 2012 Mikhail Kryshen <mikhail@kryshen.net> 7.6 +# 7.7 +# This file is part of Charamega. 7.8 +# 7.9 +# Charamega is free software: you can redistribute it and/or modify 7.10 +# it under the terms of the GNU General Public License as published by 7.11 +# the Free Software Foundation, either version 3 of the License, or 7.12 +# (at your option) any later version. 7.13 +# 7.14 +# Charamega is distributed in the hope that it will be useful, 7.15 +# but WITHOUT ANY WARRANTY; without even the implied warranty of 7.16 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 7.17 +# GNU General Public License for more details. 7.18 +# 7.19 +# You should have received a copy of the GNU General Public License 7.20 +# along with Charamega. If not, see <http://www.gnu.org/licenses/>. 7.21 +# 7.22 + 7.23 +package net.kryshen.charamega 7.24 + 7.25 +import java.awt.* 7.26 +import java.awt.event.* 7.27 +import javax.swing.* 7.28 +import java.net.URI 7.29 + 7.30 +class Ui < JPanel 7.31 + @@title = 'Charamega' 7.32 + @@version = '0.9' 7.33 + @@home = 'http://kryshen.net/games/' 7.34 + 7.35 + def initialize(root_pane:JRootPane) 7.36 + super LayoutManager(BorderLayout.new) 7.37 + 7.38 + @game = Game.new.shuffle(20) 7.39 + @field = Field.new(@game) 7.40 + @field.setBorder BorderFactory.createEmptyBorder(5, 5, 5, 5) 7.41 + 7.42 + add @field, BorderLayout.CENTER 7.43 + add create_status, BorderLayout.SOUTH 7.44 + 7.45 + ui = self 7.46 + @timer = Timer.new(250) { |e| ui.update } 7.47 + 7.48 + @conf = create_configurator 7.49 + root_pane.getLayeredPane.add @conf, JLayeredPane.MODAL_LAYER 7.50 + root_pane.setDefaultButton @start 7.51 + 7.52 + stop 7.53 + update_status 7.54 + end 7.55 + 7.56 + def doLayout 7.57 + @conf.setSize @conf.getPreferredSize 7.58 + @conf.setLocation int(getWidth * 0.05), int(getHeight * 0.05) 7.59 + super 7.60 + end 7.61 + 7.62 + def update 7.63 + @field.repaint if @game.update 7.64 + stop if @game.finished? 7.65 + update_status 7.66 + end 7.67 + 7.68 + def update_status 7.69 + @status.setText(@game.status) 7.70 + end 7.71 + 7.72 + def start 7.73 + @conf.setVisible false 7.74 + @field.setEnabled true 7.75 + @start.setEnabled false 7.76 + @stop.setEnabled true 7.77 + @game.start 7.78 + @timer.start 7.79 + @field.repaint 7.80 + end 7.81 + 7.82 + def stop 7.83 + @timer.stop 7.84 + @game.stop 7.85 + @stop.setEnabled false 7.86 + @start.setEnabled true 7.87 + @field.setEnabled false 7.88 + @conf.setVisible true 7.89 + @field.repaint 7.90 + end 7.91 + 7.92 + private 7.93 + 7.94 + def create_status 7.95 + ui = self 7.96 + 7.97 + status_panel = JPanel.new 7.98 + layout = GroupLayout.new(status_panel) 7.99 + status_panel.setLayout layout 7.100 + 7.101 + @status = JLabel.new('', SwingConstants.RIGHT) 7.102 + @stop = JButton.new('New game') 7.103 + @stop.addActionListener do |e| 7.104 + ui.update_status 7.105 + ui.stop 7.106 + end 7.107 + 7.108 + gap = 5 7.109 + 7.110 + h_group = layout.createSequentialGroup 7.111 + .addGap(1, gap, gap) 7.112 + .addComponent(@stop) 7.113 + .addGap(1, gap, gap) 7.114 + .addComponent(@status, 7.115 + GroupLayout.DEFAULT_SIZE, 7.116 + GroupLayout.DEFAULT_SIZE, 7.117 + Short.MAX_VALUE) 7.118 + .addGap(1, gap, gap) 7.119 + 7.120 + v_group = layout.createSequentialGroup 7.121 + .addGap(1, gap, gap) 7.122 + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) 7.123 + .addComponent(@stop) 7.124 + .addComponent(@status)) 7.125 + .addGap(1, gap, gap) 7.126 + 7.127 + layout.setHorizontalGroup h_group 7.128 + layout.setVerticalGroup v_group 7.129 + 7.130 + status_panel 7.131 + end 7.132 + 7.133 + def create_configurator 7.134 + # Can't use object fields in closures in Mirah 0.11. 7.135 + ui = self 7.136 + game = @game 7.137 + field = @field 7.138 + 7.139 + conf = JPanel.new 7.140 + layout = GroupLayout.new(conf) 7.141 + conf.setLayout layout 7.142 + conf.setBorder BorderFactory.createRaisedBevelBorder 7.143 + 7.144 + layout.setAutoCreateGaps true 7.145 + layout.setAutoCreateContainerGaps true 7.146 + 7.147 + title = JLabel.new(@@title) 7.148 + .setFont(Font.new('serif', Font.BOLD, 26)) 7.149 + 7.150 + version = JLabel.new("version #{@@version}") 7.151 + link = HyperlinkLabel.new(@@home) 7.152 + 7.153 + players_label = JLabel.new('Players: ') 7.154 + pairs_label = JLabel.new('Pairs: ') 7.155 + 7.156 + players = SpinnerNumberModel.new(1, 1, 20, 1) 7.157 + pairs = SpinnerNumberModel.new(20, 2, 200, 5) 7.158 + 7.159 + players_spinner = JSpinner.new(players) 7.160 + pairs_spinner = JSpinner.new(pairs) 7.161 + 7.162 + players.addChangeListener do |e| 7.163 + game.players = players.getNumber.intValue 7.164 + field.repaint if game.shuffle 7.165 + ui.update_status 7.166 + end 7.167 + 7.168 + pairs.addChangeListener do |e| 7.169 + game.shuffle pairs.getNumber.intValue 7.170 + field.repaint 7.171 + ui.update_status 7.172 + end 7.173 + 7.174 + @start = JButton.new("Start") 7.175 + @start.addActionListener do |e| 7.176 + ui.start 7.177 + end 7.178 + 7.179 + h_group = layout.createParallelGroup 7.180 + .addGroup(layout.createSequentialGroup 7.181 + .addComponent(title) 7.182 + .addComponent(version)) 7.183 + .addGroup(layout.createSequentialGroup 7.184 + .addComponent(link, 7.185 + GroupLayout.DEFAULT_SIZE, 7.186 + GroupLayout.DEFAULT_SIZE, 7.187 + GroupLayout.PREFERRED_SIZE) 7.188 + .addGap(0, 0, Short.MAX_VALUE)) 7.189 + .addGroup(layout.createSequentialGroup 7.190 + .addGap(0, 0, Short.MAX_VALUE) 7.191 + .addGroup(layout.createParallelGroup 7.192 + .addComponent(pairs_label) 7.193 + .addComponent(players_label)) 7.194 + .addGroup(layout.createParallelGroup 7.195 + .addComponent(pairs_spinner) 7.196 + .addComponent(players_spinner)) 7.197 + .addGap(0, 0, Short.MAX_VALUE)) 7.198 + .addGroup(layout.createSequentialGroup 7.199 + .addGap(0, 0, Short.MAX_VALUE) 7.200 + .addComponent(@start)) 7.201 + 7.202 + v_group = layout.createSequentialGroup 7.203 + .addGroup(layout.createBaselineGroup(false, true) 7.204 + .addComponent(title) 7.205 + .addComponent(version)) 7.206 + .addComponent(link) 7.207 + .addGap(25) 7.208 + .addGroup(layout.createBaselineGroup(false, true) 7.209 + .addComponent(pairs_label) 7.210 + .addComponent(pairs_spinner)) 7.211 + .addGroup(layout.createBaselineGroup(false, true) 7.212 + .addComponent(players_label) 7.213 + .addComponent(players_spinner)) 7.214 + .addGap(15) 7.215 + .addComponent(@start) 7.216 + 7.217 + layout.setHorizontalGroup h_group 7.218 + layout.setVerticalGroup v_group 7.219 + 7.220 + conf 7.221 + end 7.222 +end 7.223 + 7.224 +class HyperlinkLabel < JLabel 7.225 + 7.226 + def initialize(uri:String) 7.227 + initialize URI.new(uri), uri 7.228 + end 7.229 + 7.230 + def initialize(uri:URI) 7.231 + initialize uri, uri.toString 7.232 + end 7.233 + 7.234 + def initialize(uri:URI, text:String) 7.235 + @uri = uri 7.236 + 7.237 + s = text.replace('&', '&') 7.238 + .replace('<', '<') 7.239 + .replace('>', '>') 7.240 + 7.241 + setText "<html><u>#{s}</u></html>" 7.242 + setForeground Color.BLUE.darker 7.243 + setCursor Cursor.new(Cursor.HAND_CURSOR) 7.244 + 7.245 + enableEvents AWTEvent.MOUSE_EVENT_MASK 7.246 + end 7.247 + 7.248 + def processMouseEvent(event) 7.249 + if event.getID == MouseEvent.MOUSE_CLICKED 7.250 + Desktop.getDesktop.browse @uri 7.251 + end 7.252 + 7.253 + super 7.254 + end 7.255 +end 7.256 + 7.257 +# UIManager.setLookAndFeel UIManager.getSystemLookAndFeelClassName 7.258 + 7.259 +frame = JFrame.new 'Charamega' 7.260 +ui = Ui.new(frame.getRootPane) 7.261 + 7.262 +frame.getContentPane.add ui 7.263 +frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE) 7.264 + .setSize(800, 600) 7.265 + .validate 7.266 + .setVisible(true)