Skip to content

Commit

Permalink
add /tiles/test to easily reference tiles from different titles and g…
Browse files Browse the repository at this point in the history
…ame states

* can display a tile as it appears on the board at any given point in any given
  fixture

    * fixtures are processed to the specified action, and the select tiles are
      rendered

* include tiles showing the problems reported in the following issues:

    * tobymao#4992

    * tobymao#5153

    * tobymao#5167

    * tobymao#5673

        * copy 18Mag fixture 76622 to a new fixture to reproduce

    * tobymao#5981

    * tobymao#6604

    * tobymao#7765

    * tobymao#8178

* add completed 1868 Wyoming game as a fixture

    * 1846 and 1868 Wyoming tiles added here were used for the before/after
      screenshots in tobymao#9174

* move fixtures from `/spec/fixtures/` into `/public/fixtures/` so the JSON can
  be accessed via `/fixtures/<title>/<fixture_id>.json`

* test cases defined in a location accessible by both the view code and server
  side code (`lib/engine/test_tiles.rb`) so that when the page is initially
  served up, it already includes the `<script>` tags for the games being tested

    * test cases need a `title`, can also have `title`, `fixture`, `action`, and
      any `kwargs` used by `render_tile_blocks`
  • Loading branch information
michaeljb committed May 11, 2023
1 parent dbb4ebe commit 98e222c
Show file tree
Hide file tree
Showing 203 changed files with 793 additions and 12 deletions.
12 changes: 11 additions & 1 deletion api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
require_relative 'models'
require_rel './lib'
require_rel './models'
require_relative 'lib/engine/test_tiles'

class Api < Roda
opts[:check_dynamic_arity] = false
Expand Down Expand Up @@ -132,7 +133,16 @@ class Api < Roda

r.on 'tiles' do
parts = request.path.split('/')
titles = parts.size == 4 ? parts[2].split(/[+ ]/) : []

titles =
if parts.size == 4
parts[2].split(/[+ ]/)
elsif parts[2] == 'test'
Engine::TestTiles::TEST_TILES.keys.compact
else
[]
end

render(titles: titles)
end

Expand Down
2 changes: 1 addition & 1 deletion assets/app/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def render_content
when /about/
h(View::About)
when /tiles/
h(View::TilesPage, route: @app_route)
h(View::TilesPage, route: @app_route, connection: @connection)
when /map/
h(View::MapPage, route: @app_route)
when /market/
Expand Down
43 changes: 39 additions & 4 deletions assets/app/view/tiles.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ class Tiles < Snabberb::Component
padding: '0 0.7rem 0 0.2rem',
},
}.freeze
BOTTOM_LINE_PROPS = {
style: {
height: '0.9rem',
padding: '0 0.7rem 0 0.2rem',
bottom: '3px',
position: 'absolute',
},
}.freeze
TEXT_PROPS = {
style: {
float: 'left',
Expand All @@ -39,29 +47,51 @@ def render_tile_blocks(
rotations: nil,
hex_coordinates: nil,
clickable: false,
extra_children: []
location_on_plain: false,
extra_children: [],
top_text: nil,
fixture_id: nil,
fixture_title: nil,
action: nil
)
block_props = {
style: {
width: "#{WIDTH * scale}px",
height: "#{HEIGHT * scale}px",
position: 'relative',
},
}

tile ||= Engine::Tile.for(name)

loc_name = location_name || tile.location_name if (tile.cities + tile.towns + tile.offboards).any?
loc_name = location_name || tile.location_name if !(tile.cities + tile.towns + tile.offboards).empty? || location_on_plain

rotations = [0] if tile.preprinted || !rotations

rotations.map do |rotation|
tile.rotate!(rotation)

unless setting_for(@hide_tile_names)
if setting_for(@hide_tile_names)
text = nil
elsif top_text
text = top_text
else
text = tile.preprinted ? '' : '#'
text += name
text += "-#{rotation}" unless rotations == [0]
end

text += "-#{rotation}" if !setting_for(@hide_tile_names) && rotations != [0]

bottom_text = ''
if fixture_id
bottom_text = fixture_id
href = "/fixture/#{fixture_title}/#{fixture_id}"
if action
bottom_text += " action=#{action}"
href += "?action=#{action}"
end
end

count = tile.unlimited ? '∞' : num.to_s

hex = Engine::Hex.new(hex_coordinates || 'A1',
Expand All @@ -88,6 +118,11 @@ def render_tile_blocks(
),
]),
]),
h(:div, BOTTOM_LINE_PROPS, [
h(:div, TEXT_PROPS, [
h(:a, { attrs: { href: href } }, bottom_text),
]),
]),
])
end
end
Expand Down
119 changes: 115 additions & 4 deletions assets/app/view/tiles_page.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
# frozen_string_literal: true

require 'game_manager'
require 'lib/connection'
require 'engine/test_tiles'
require 'lib/params'
require 'view/tiles'
require_relative '../game_class_loader'

module View
class TilesPage < Tiles
include GameClassLoader
include GameManager

needs :route
needs :fixture_data, default: {}, store: true

ROUTE_FORMAT = %r{/tiles/([^/?]*)(?:/([^?]+))?}.freeze

Expand Down Expand Up @@ -51,6 +54,10 @@ def render

])

elsif dest == 'test'

render_test_tiles

elsif dest == 'custom'
location_name = Lib::Params['n']
color = Lib::Params['c'] || 'yellow'
Expand Down Expand Up @@ -112,7 +119,7 @@ def render
end
end

def render_individual_tile_from_game(game, hex_or_tile_id)
def render_individual_tile_from_game(game, hex_or_tile_id, scale: 3.0, **kwargs)
id, rotation = hex_or_tile_id.split('-')
rotations = rotation ? [rotation.to_i] : @rotations

Expand All @@ -132,9 +139,10 @@ def render_individual_tile_from_game(game, hex_or_tile_id)
layout: game.class::LAYOUT,
tile: tile,
location_name: tile.location_name || @location_name,
scale: 3.0,
scale: scale,
rotations: rotations,
hex_coordinates: hex_coordinates,
**kwargs,
)
end

Expand Down Expand Up @@ -246,5 +254,108 @@ def render_toggle_button
h(:'button.small', { on: { click: toggle } }, "Tile Names #{setting_for(@hide_tile_names) ? '❌' : '✅'}"),
])
end

def render_test_tiles
# see /lib/engine/test_tiles.rb
test_tiles = Engine::TestTiles::TEST_TILES

scale = 2.0

return h(:div, [h(:p, 'Loading...')]) unless @connection

rendered_test_tiles = []

test_tiles.each do |title, fixtures|
if title
game_class = load_game_class(title)
else
fixtures[nil][nil].each do |hex_or_tile, opts|
%i[flat pointy].map do |layout_|
rendered_test_tiles.concat(
render_tile_blocks(hex_or_tile, layout: layout_, scale: scale, rotations: @rotations, **opts)
)
end
end
next
end

fixtures.each do |fixture, actions|
if fixture
if @fixture_data[fixture]
actions.each do |action, hex_or_tiles|
kwargs = action ? { at_action: action } : {}

game = Engine::Game.load(@fixture_data[fixture], **kwargs)

hex_or_tiles.each do |hex_or_tile, opts|
hex_coordinates = hex_or_tile
tile = game.hex_by_id(hex_coordinates).tile

rendered_test_tiles.concat(
render_tile_blocks(
hex_coordinates,
layout: game_class::LAYOUT,
tile: tile,
location_name: tile.location_name,
location_on_plain: true,
scale: scale,
rotations: [tile.rotation],
hex_coordinates: hex_coordinates,
name_prefix: title,
top_text: "#{title}: #{hex_or_tile}",
fixture_id: fixture,
fixture_title: title,
action: action,
**opts,
)
)
end
end

elsif @connection
# load the fixture game data
@connection.get("/fixtures/#{title}/#{fixture}.json", '') do |data|
@fixture_data[fixture] = data
store(:fixture_data, @fixture_data, skip: false)
end

# render placeholder tiles which will be replaced once the
# appropriate fixture is loaded and processed
actions.each do |action, hex_or_tiles|
hex_or_tiles.each do |hex_or_tile, opts|
rendered_test_tiles.concat(
render_tile_blocks(
'blank',
layout: game_class::LAYOUT,
location_name: 'Loading Fixture...',
location_on_plain: true,
scale: scale,
name_prefix: title,
top_text: "#{title}: #{hex_or_tile}",
fixture_id: fixture,
fixture_title: title,
action: action,
**opts
)
)
end
end
end

else
players = Array.new(game_class::PLAYER_RANGE.max) { |n| "Player #{n + 1}" }
game = game_class.new(players)
actions[nil].each do |hex_or_tile, opts|
rendered_test_tiles.concat(
Array(render_individual_tile_from_game(game, hex_or_tile, scale: scale, top_text: "#{title}: #{hex_or_tile}",
**opts))
)
end
end
end
end

h('div#tiles', rendered_test_tiles)
end
end
end
80 changes: 80 additions & 0 deletions lib/engine/test_tiles.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# frozen_string_literal: true

module Engine
module TestTiles
# each entry is a Hash containing:
# - tile / hex id
# - game title (optional)
# - fixture (optional, requires game title; if not given, the starting state
# of the hex/tile is used)
# - action id (optional, requires fixture; if not given, the fixture is
# processed to its conclusion)
# - other kwargs for View::Tiles#render_tile_blocks
TEST_TILES_HUMAN_READABLE = [
{ tile: '45' },

{ tile: 'H11', title: '1822PNW' },
{ tile: 'O8', title: '1822PNW' },
{ tile: 'I12', title: '1822PNW' },

# open: https://github.com/tobymao/18xx/issues/5981
{ tile: 'H22', title: '1828.Games' },

# open: https://github.com/tobymao/18xx/issues/8178
{ tile: 'H18', title: '1830', fixture: '26855', action: 385 },

{ tile: 'C15', title: '1846' },

# open: https://github.com/tobymao/18xx/issues/5167
{ tile: 'N11', title: '1856', fixture: 'hotseat005', action: 113 },

{ tile: 'L0', title: '1868 Wyoming' },
{ tile: 'WRC', title: '1868 Wyoming' },
{ tile: 'F12', title: '1868 Wyoming', fixture: '1868WY_5', action: 835 },
{ tile: 'L0', title: '1868 Wyoming', fixture: '1868WY_5', action: 835 },
{ tile: 'J12', title: '1868 Wyoming', fixture: '1868WY_5', action: 835 },
{ tile: 'J12', title: '1868 Wyoming', fixture: '1868WY_5' },

# open: https://github.com/tobymao/18xx/issues/4992
{ tile: 'I11', title: '1882', fixture: '5236', action: 303 },

# open: https://github.com/tobymao/18xx/issues/6604
{ tile: 'L41', title: '1888' },

# open: https://github.com/tobymao/18xx/issues/5153
{ tile: 'IR7', title: '18Ireland' },
{ tile: 'IR8', title: '18Ireland' },

# open: https://github.com/tobymao/18xx/issues/5673
{ tile: 'D19', title: '18Mag', fixture: 'hs_tfagolvf_76622' },
{ tile: 'I14', title: '18Mag', fixture: 'hs_tfagolvf_76622' },

# open: https://github.com/tobymao/18xx/issues/7765
{ tile: '470', title: '18MEX' },
{ tile: '475', title: '18MEX' },
{ tile: '479P', title: '18MEX' },
{ tile: '485P', title: '18MEX' },
{ tile: '486P', title: '18MEX' },
].freeze

# rearrange the above to a structure that can be more efficiently iterated
# over--each fixture only needs to be fetched once, and only needs to be
# processed to each unique action once
#
# defining with this structure directly would confusing to read; for generic
# tiles, all of the keys in the nested Hash would end up as `nil`
TEST_TILES =
TEST_TILES_HUMAN_READABLE.each_with_object({}) do |opts, test_tiles|
tile = opts.delete(:tile)
title = opts.delete(:title)
fixture = opts.delete(:fixture)
action = opts.delete(:action)

test_tiles[title] ||= {}
test_tiles[title][fixture] ||= {}
test_tiles[title][fixture][action] ||= []

test_tiles[title][fixture][action] << [tile, opts]
end.freeze
end
end
1 change: 0 additions & 1 deletion public/fixtures

This file was deleted.

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions public/fixtures/1868 Wyoming/1868WY_5.json

Large diffs are not rendered by default.

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 98e222c

Please sign in to comment.