Skip to content

Commit

Permalink
Initial checkin
Browse files Browse the repository at this point in the history
  • Loading branch information
ottolin committed Jan 25, 2015
0 parents commit 0424e97
Show file tree
Hide file tree
Showing 10 changed files with 330 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
@@ -0,0 +1,5 @@
/_build
/cover
/deps
erl_crash.dump
*.ez
4 changes: 4 additions & 0 deletions README.md
@@ -0,0 +1,4 @@
Exmj
====

** TODO: Add description **
26 changes: 26 additions & 0 deletions config/config.exs
@@ -0,0 +1,26 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config

# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for third-
# party users, it should be done in your mix.exs file.

# Sample configuration:
#
# config :logger,
# level: :info
#
# config :logger, :console,
# format: "$date $time [$level] $metadata$message\n",
# metadata: [:user_id]

# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"
2 changes: 2 additions & 0 deletions lib/exmj.ex
@@ -0,0 +1,2 @@
defmodule Exmj do
end
122 changes: 122 additions & 0 deletions lib/gameset.ex
@@ -0,0 +1,122 @@
defmodule GameSet do

def win(tiles, fixed_tiles \\ []) do
wp = do_winpattern(tiles, fixed_tiles)
case wp do
nil -> :not_win
[] -> :not_win
_ -> {:win, count_fan(wp)}
end
end

def winpattern(tiles) do
do_winpattern(tiles, [])
end

# 2 tiles left. finding a pair of eye
defp do_winpattern(tiles, fixed_tiles) when length(tiles) == 2 do
case Tile.same(tiles) do
true -> {[tiles | fixed_tiles]} # putting each win pattern list into a tuple to prevent flattening
_else -> :no_win_pattern
end
end

# more than 2 tiles left.
defp do_winpattern(tiles, fixed_tiles) when rem(length(tiles),3) == 2 do
case Tile.find_3(tiles) do
[] -> :no_win_pattern
fixed_list -> Enum.map(fixed_list, fn fixed ->
do_winpattern(tiles -- fixed, [fixed | fixed_tiles])
end)
|> List.flatten
|> Enum.filter( fn result -> result != :no_win_pattern end )
end
end

defp do_winpattern(_, _) do
nil
end

defp count_fan(win_patterns) do
win_patterns
|> Enum.map( fn {inner_win_pattern} -> do_count_fan(inner_win_pattern) end )
|> Enum.reduce( fn ({{fan, _fan_types}, win_pattern} = cur, {{acc_fan, _acc_fan_types}, acc_win_pattern} = acc) ->
cond do
fan > acc_fan -> cur
true -> acc
end
end )
end

# Returning a tuple of { {total_number_of_fans, [ {number_of_fans, fan_pattern_type} ]}, original_win_pattern }
defp do_count_fan(win_pattern) do
mp = map_pattern(win_pattern)
fans = [pungpung_fan(mp) ,
pingwu_fan(mp) ,
category_fan(win_pattern)]
|> Enum.filter( fn {nfan, _fan_type} -> nfan > 0 end )

# Summing all different fan types to get the final number of fan
total_fan = fans |> Enum.reduce(0, fn ({nfan, _fan_type}, acc) -> acc + nfan end)
{{total_fan, fans}, win_pattern}
end

# map the win pattern from winpattern() call to another pattern for easier counting fan
defp map_pattern(win_pattern) do
[_eye| remain] = win_pattern
# We dont care the pair of eye.
# eye is always at position 0 due to the recursion checking
remain
|> Enum.map( fn pattern ->
cond do
Tile.pung(pattern) -> :Pung
Tile.sheung(pattern) -> :Sheung
true -> :Wrong
# todo: check flowers
end
end)
end

# Returning a tuple of {number_of_fans, :Pung}
defp pungpung_fan(mp) do
all_pung = mp |> Enum.reduce(true, fn (pattern, acc) -> acc && (pattern == :Pung) end)
nfan = case all_pung do
true -> 3
_else -> 0
end
{nfan, :Pung}
end

# Returning a tuple of {number_of_fans, :PingWu}
defp pingwu_fan(mp) do
all_pung = mp |> Enum.reduce(true, fn (pattern, acc) -> acc && (pattern == :Sheung) end)
nfan = case all_pung do
true -> 1
_else -> 0
end
{nfan, :PingWu}
end

# Returning a tuple of {number_of_fans, :SameCat | :MixCat}
defp category_fan(wp) do
categories = wp |> List.flatten
|> Enum.filter(fn tile -> tile.cat != :Flower end) # Skip flower when counting category fan
|> Enum.reduce([], fn (tile, acc) -> [tile.cat| acc] end)
|> Enum.uniq

case length(categories) do
1 -> {7, :SameCat} # All categories are the same for full hand.
2 -> cond do
:Fan in categories -> {3, :MixCat} # Fan + another category
true -> {0, :MixCat} # Mixing of Bamboo/Character/Dot
end
_Other -> {0, :MixCat}
end
end

# Returning a tuple of {number_of_fans, :SameCat | :MixCat}
defp dragon_fan(wp) do
end

end

3 changes: 3 additions & 0 deletions lib/player.ex
@@ -0,0 +1,3 @@
defmodule Player do

end
129 changes: 129 additions & 0 deletions lib/tile.ex
@@ -0,0 +1,129 @@
defmodule Tile do
# category can be :Bamboo :Character :Dot :Fan :Flower
# sub-category can be :Bamboo :Character :Dot :Dragon :Wind :Flower
# num from 1 - 9 for Bamboo/Character/Dot
# num from 1 - 7 for Fan (Dragon/Wind)
# num from 1 - 8 for Flower
defstruct cat: :invalid, sub: :invalid, num: 0

def pingwu do
[ %Tile{cat: :Dot, sub: :Dot, num: 1},
%Tile{cat: :Dot, sub: :Dot, num: 1},
%Tile{cat: :Dot, sub: :Dot, num: 1},
%Tile{cat: :Dot, sub: :Dot, num: 2},
%Tile{cat: :Dot, sub: :Dot, num: 2},
%Tile{cat: :Dot, sub: :Dot, num: 2},
%Tile{cat: :Dot, sub: :Dot, num: 3},
%Tile{cat: :Dot, sub: :Dot, num: 3},
%Tile{cat: :Dot, sub: :Dot, num: 3},
%Tile{cat: :Dot, sub: :Dot, num: 4},
%Tile{cat: :Dot, sub: :Dot, num: 5},
%Tile{cat: :Dot, sub: :Dot, num: 6},
%Tile{cat: :Bamboo, sub: :Bamboo, num: 1},
%Tile{cat: :Bamboo, sub: :Bamboo, num: 1}
]
end

def gg() do
[ %Tile{cat: :Dot, sub: :Dot, num: 1},
%Tile{cat: :Dot, sub: :Dot, num: 1},
%Tile{cat: :Dot, sub: :Dot, num: 1},
%Tile{cat: :Dot, sub: :Dot, num: 2},
%Tile{cat: :Dot, sub: :Dot, num: 2},
%Tile{cat: :Dot, sub: :Dot, num: 2},
%Tile{cat: :Dot, sub: :Dot, num: 3},
%Tile{cat: :Dot, sub: :Dot, num: 3},
%Tile{cat: :Dot, sub: :Dot, num: 3},
%Tile{cat: :Dot, sub: :Dot, num: 4},
%Tile{cat: :Dot, sub: :Dot, num: 5},
%Tile{cat: :Dot, sub: :Dot, num: 6},
%Tile{cat: :Dot, sub: :Dot, num: 7},
%Tile{cat: :Dot, sub: :Dot, num: 7}
]
end

def same(tiles) do
[h|_T] = tiles
tiles
|> Enum.reduce(true, fn(t,acc) -> acc && (t.sub == h.sub) && (t.num == h.num) end)
end

def valid_3([x, y, z]) do
sheung([x, y, z]) or pung([x, y, z])
end

def same_sub_3([x, y, z]) do
x.sub == y.sub &&
y.sub == z.sub
end

def consec_3([x, y, z]) do
mag = [x.num, y.num, z.num]
|> Enum.sort
[min|_T] = mag
[0, 1, 2] == mag |> Enum.map(fn n -> n - min end)
end

def pung([x, y, z]) do
same([x, y, z])
end

def sheung([x, y, z]) do
same_sub_3([x, y, z]) && consec_3([x, y, z])
end

def find_3(tiles) do
# Assigning an id to it for smaller search range to find permutation
t_tuples = Enum.zip(tiles |> Enum.sort(fn (l, r) -> l.num < r.num end ), 1..144)

(for x <- t_tuples, y <- t_tuples -- [x], z <- (t_tuples -- [x]) -- [y], valid_3([elem(x, 0), elem(y, 0), elem(z, 0)]), elem(x, 1) <= elem(y, 1), elem(y, 1) <= elem(z, 1), do: [elem(x, 0), elem(y, 0), elem(z, 0)])
|> Enum.uniq # do not count the same permutation twice for faster searching win pattern
end

def all(flowers \\ false) do
# Every tile has 4 pieces
all_patterns = (all_dots ++ all_bamboos ++ all_characters ++ all_fans)
|> Enum.flat_map( fn x -> [x,x,x,x] end )
all_tiles = case flowers do
true -> all_patterns ++ all_flowers
_else -> all_patterns
end

# Assigning an id to it for easier to find permutation later on
# Enum.zip(all_tiles, 1..144) |> Enum.map( fn tuple -> %{elem(tuple, 0) | id: elem(tuple, 1)} end )
all_tiles
end

defp all_bamboos do
get_tiles(:Bamboo, :Bamboo, 9)
end

defp all_characters do
get_tiles(:Character, :Character, 9)
end

defp all_dots do
get_tiles(:Dot, :Dot, 9)
end

defp all_fans do
all_winds ++ all_dragons
end

defp all_winds do
get_tiles(:Fan, :Wind, 4)
end

defp all_dragons do
get_tiles(:Fan, :Dragon, 7, 5)
end

defp all_flowers do
get_tiles(:Flower, :Flower, 8)
end

defp get_tiles(cat, sub, to, from \\ 1) do
from..to
|> Enum.map(fn i -> %Tile{cat: cat, sub: sub, num: i} end)
end
end
30 changes: 30 additions & 0 deletions mix.exs
@@ -0,0 +1,30 @@
defmodule Exmj.Mixfile do
use Mix.Project

def project do
[app: :exmj,
version: "0.0.1",
elixir: "~> 1.1-dev",
deps: deps]
end

# Configuration for the OTP application
#
# Type `mix help compile.app` for more information
def application do
[applications: [:logger]]
end

# Dependencies can be Hex packages:
#
# {:mydep, "~> 0.3.0"}
#
# Or git/path repositories:
#
# {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"}
#
# Type `mix help deps` for more examples and options
defp deps do
[]
end
end
8 changes: 8 additions & 0 deletions test/exmj_test.exs
@@ -0,0 +1,8 @@
defmodule ExmjTest do
use ExUnit.Case
doctest Exmj

test "the truth" do
assert 1 + 1 == 2
end
end
1 change: 1 addition & 0 deletions test/test_helper.exs
@@ -0,0 +1 @@
ExUnit.start()

0 comments on commit 0424e97

Please sign in to comment.