Last active
August 29, 2015 14:22
-
-
Save mat3u/068b79ea838d0f8d83b0 to your computer and use it in GitHub Desktop.
Engine for TronAI game - bots playing TRON
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type Direction = | North | East | South | West | |
type Position = int * int | |
type Size = int * int | |
type Player = int | |
type World = { Taken : Position list; Heads : (Player * Position) list } | |
type Bot = Player -> Position -> World -> Direction | |
let skipLast list = | |
// TODO: Ultra inefficient | |
list |> List.rev |> List.tail |> List.rev | |
let moveHead (x,y) direction = | |
match direction with | |
| North -> (x, y-1) | |
| East -> (x+1, y) | |
| South -> (x, y+1) | |
| West -> (x-1, y) | |
let randomPosition (r: System.Random) ((w,h): Size) = | |
(r.Next(w), r.Next(h)) | |
let initializeGame (r: System.Random) ((w,h): Size) (players: Player seq) = | |
let heads = List.ofSeq (seq { | |
for p in players do | |
yield (p, randomPosition r (w,h)) | |
}) | |
let border = List.ofSeq ((seq { | |
for x in [-1; w] do | |
for y in -1 .. h do | |
yield (x,y) | |
for x in -1..w do | |
for y in [-1;h] do | |
yield (x,y) | |
yield! heads |> Seq.map snd | |
}) |> Seq.distinct) | |
{Taken = border; Heads = heads} | |
let turn (bots: (Player * Bot) seq) (world: World) : World = | |
let participants = query { | |
for bot in bots do | |
join player in world.Heads | |
on (fst(bot) = fst(player)) | |
select (fst(bot), snd(bot), snd(player)) | |
} | |
let potentialMoves = participants | |
|> Seq.toList | |
|> List.map (fun (player, bot, position_n) -> (player, bot player position_n world, position_n)) | |
|> List.map (fun (player, direction, position_n) -> (player, moveHead position_n direction)) | |
let potentialTakens = potentialMoves | |
|> List.scan (fun taken (_, position) -> position :: taken) world.Taken | |
|> skipLast | |
let survivors = List.zip potentialMoves potentialTakens | |
|> List.choose (fun ((player, position), taken) -> | |
match (taken |> List.exists (fun c -> c = position)) with | |
| false -> Some (player, position) | |
| _ -> None | |
) |> Seq.toList | |
let taken = world.Taken |> Seq.append (survivors |> Seq.map snd) |> Seq.toList | |
{Taken = taken; Heads = survivors} | |
let game (r: System.Random) (size: Size) (bots: (Player * Bot) seq) = | |
let initialWorld = initializeGame r size (bots |> Seq.map fst) | |
let turn' = turn (bots |> Seq.toList) | |
initialWorld | |
|> Seq.unfold (fun world -> | |
match world.Heads |> Seq.length with | |
| 0 -> None | |
| 1 -> None | |
| _ -> | |
let next = turn' world | |
Some (next, next) | |
) | |
open System | |
let render (w, h) worlds = | |
let colors = [ConsoleColor.Red; ConsoleColor.Blue; ConsoleColor.Cyan; ConsoleColor.Yellow; ConsoleColor.Green; ConsoleColor.Magenta] | |
let renderTurn (w, h) world = | |
world.Heads |> Seq.iter (fun (player, (x, y)) -> | |
let org = Console.ForegroundColor | |
Console.ForegroundColor <- colors.[player % colors.Length] | |
Console.SetCursorPosition(x+1, y+1) | |
Console.Write(player) | |
Console.ForegroundColor <- org | |
System.Threading.Thread.Sleep(10) | |
) | |
let initial::history = worlds | |
initial.Taken |> Seq.iter (fun (x, y) -> | |
Console.SetCursorPosition(x+1, y+1) | |
Console.Write("#") | |
) | |
worlds |> Seq.iter (renderTurn (w,h)) | |
let kingOfTheNorth (_ : Player) (_: Position) (_: World) = | |
North | |
let wriggler (me : Player) ((x,y): Position) (world: World) = | |
let isEmpty potential = | |
world.Taken |> Seq.exists (fun c -> c = potential) |> not | |
if isEmpty (x, y-1) then North | |
else if isEmpty (x-1, y) then West | |
else if isEmpty (x+1, y) then East | |
else South | |
let wrigglerL (_ : Player) ((x,y): Position) (world: World) = | |
let isEmpty potential = | |
world.Taken |> Seq.exists (fun c -> c = potential) |> not | |
if isEmpty (x, y+1) then South | |
else if isEmpty (x+1, y) then East | |
else if isEmpty (x-1, y) then West | |
else North | |
let loony (_ : Player) ((x,y): Position) (world: World) = | |
let r = new System.Random(x * y) | |
let isEmpty potential = | |
world.Taken |> Seq.exists (fun c -> c = potential) |> not | |
let options = seq { | |
yield ((x, y+1), South) | |
yield ((x, y-1), North) | |
yield ((x+1, y), East) | |
yield ((x-1, y), West) | |
} | |
let valid = options | |
|> Seq.choose (fun (p, d) -> | |
match isEmpty p with | |
| false -> None | |
| _ -> Some d | |
) |> Seq.toList | |
match valid.Length with | |
| 0 -> North | |
| n -> valid.[r.Next(n)] | |
[<EntryPoint>] | |
let main argv = | |
let random = new System.Random() | |
let size = (70, 35) | |
let bots = [wriggler; wrigglerL; loony; loony; kingOfTheNorth; wriggler] | |
|> List.mapi (fun i b -> (i, b)) | |
let history = game random size bots |> Seq.toList | |
history |> render size | |
let survivors = (history |> Seq.last).Heads | |
match survivors.Length with | |
| 0 -> Console.Write(" DRAW ") | |
| 1 -> Console.Write("<- WINNER ") | |
Console.ReadKey() | |
0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment